{"version":3,"sources":["../src/browser-user-timing.ts"],"names":[],"mappings":"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","file":"browser-user-timing.mjs","sourcesContent":["import type { Profiler } from './profiler.js';\nimport type { ProfileDetail, RecorderSession } from './recorder.js';\nimport type { ProfileCapture, ProfilePhaseStart, ProfileSource } from './types.js';\n\n/** The small browser API surface used by the opt-in User Timing adapter. */\ntype UserTiming = { readonly mark: (name: string) => void };\n\n/** Configuration shared by editor and standalone Play browser hosts. */\nexport interface UserTimingProfilerOptions {\n  /** Global flag that enables marks. Defaults to the harness diagnostic flag. */\n  readonly diagnosticsKey?: string;\n  /** Stable id written into the optional Profiler capture projection. */\n  readonly captureId?: string;\n  /** Host callback invoked after an owner phase closes. */\n  readonly onPhaseEnd?: (phase: { readonly source: ProfileSource; readonly phase: string }) => void;\n}\n\ntype DiagnosticGlobal = Record<string, unknown>;\ntype PhaseCatalog = ProfileCapture['phaseCatalog'];\n\nfunction partialCapture(captureId: string, phaseCatalog: PhaseCatalog): ProfileCapture {\n  return {\n    schemaVersion: '1.0',\n    captureId,\n    timeUnit: 'microseconds',\n    frameLimit: 1,\n    eventLimit: 1,\n    phaseCatalog,\n    records: [],\n    completeness: {\n      status: 'partial',\n      retainedEventCount: 0,\n      droppedEventCount: 0,\n      incompleteReason: 'user-timing-transport',\n    },\n  };\n}\n\nfunction diagnosticsEnabled(key: string): boolean {\n  const value = (globalThis as unknown as DiagnosticGlobal)[key];\n  return (\n    typeof value === 'object' &&\n    value !== null &&\n    (value as { readonly enabled?: unknown }).enabled === true\n  );\n}\n\nfunction diagnosticsDetail(key: string): ProfileDetail {\n  const value = (globalThis as unknown as DiagnosticGlobal)[key];\n  if (typeof value !== 'object' || value === null) return 'owner';\n  const detail = (value as { readonly detail?: unknown }).detail;\n  return detail === 'nested' || detail === 'passes' ? detail : 'owner';\n}\n\n/**\n * Creates a zero-cost-while-disabled browser projection of the engine Profiler.\n *\n * The engine frame loop remains the phase owner. This adapter only translates\n * the already-open phases to User Timing marks when a host explicitly enables\n * the diagnostic flag before app creation. Normal game/editor frames therefore\n * do not allocate a profiler or call performance.mark().\n */\nexport function createUserTimingProfiler(\n  options: UserTimingProfilerOptions = {},\n): Profiler | undefined {\n  const diagnosticsKey = options.diagnosticsKey ?? '__forgeaxFramePhaseDiagnostics';\n  const captureId = options.captureId ?? 'forgeax-user-timing';\n  const timingEnabled = diagnosticsEnabled(diagnosticsKey);\n  const detail = diagnosticsDetail(diagnosticsKey);\n  const performanceApi = timingEnabled\n    ? (globalThis as unknown as { readonly performance?: UserTiming }).performance\n    : undefined;\n  if (!timingEnabled && options.onPhaseEnd === undefined) return undefined;\n  if (\n    timingEnabled &&\n    (performanceApi === undefined || typeof performanceApi.mark !== 'function')\n  ) {\n    if (options.onPhaseEnd === undefined) return undefined;\n  }\n\n  let phaseCatalog: PhaseCatalog = { app: [], render: [] };\n  let frameId: number | undefined;\n  let active = true;\n  let latest: ProfileCapture | undefined;\n  const openPhases: Array<{ readonly source: ProfileSource; readonly phase: string }> = [];\n\n  function mark(name: string): void {\n    if (performanceApi === undefined) return;\n    try {\n      performanceApi.mark(name);\n    } catch {\n      // Diagnostics must not change the host frame-loop behavior.\n    }\n  }\n\n  function timingSource(source: ProfileSource): 'frame' | 'render' {\n    return source === 'app' ? 'frame' : 'render';\n  }\n\n  const session = {\n    captureId,\n    detail,\n    beginFrame(nextFrameId: number) {\n      frameId = nextFrameId;\n      openPhases.length = 0;\n      return { ok: true as const, value: undefined };\n    },\n    beginPhase(inputOrSource: ProfilePhaseStart | ProfileSource, phaseName?: string) {\n      if (frameId === undefined) return { ok: true as const, value: undefined };\n      const input =\n        typeof inputOrSource === 'string'\n          ? { source: inputOrSource, phase: phaseName as string }\n          : inputOrSource;\n      openPhases.push(input);\n      mark(`forgeax.${timingSource(input.source)}.phase.${frameId}.${input.phase}.begin`);\n      return { ok: true as const, value: undefined };\n    },\n    endPhase() {\n      const phase = openPhases.pop();\n      if (phase !== undefined) options.onPhaseEnd?.(phase);\n      if (frameId !== undefined && phase !== undefined) {\n        mark(`forgeax.${timingSource(phase.source)}.phase.${frameId}.${phase.phase}.end`);\n      }\n      return { ok: true as const, value: undefined };\n    },\n    recordSkip(input: {\n      readonly source: ProfileSource;\n      readonly phase: string;\n      readonly reason: string;\n    }) {\n      if (frameId !== undefined) {\n        mark(\n          `forgeax.${timingSource(input.source)}.phase.${frameId}.${input.phase}.skip.${input.reason}`,\n        );\n      }\n      return { ok: true as const, value: undefined };\n    },\n    endFrame() {\n      frameId = undefined;\n      openPhases.length = 0;\n      return { ok: true as const, value: undefined };\n    },\n    finish() {\n      latest = partialCapture(captureId, phaseCatalog);\n      active = false;\n      frameId = undefined;\n      openPhases.length = 0;\n      return { ok: true as const, value: latest };\n    },\n  } satisfies RecorderSession;\n\n  return {\n    registerPhaseCatalog(source, phases) {\n      const definition = [...phases];\n      phaseCatalog = { ...phaseCatalog, [source]: definition };\n      let registered = true;\n      return {\n        ok: true,\n        value: () => {\n          if (!registered) return;\n          registered = false;\n          if (phaseCatalog[source] !== definition) return;\n          phaseCatalog = { ...phaseCatalog, [source]: [] };\n        },\n      };\n    },\n    startCapture() {\n      active = true;\n      latest = undefined;\n      return { ok: true, value: session };\n    },\n    activeCaptureId() {\n      return active ? captureId : undefined;\n    },\n    activeSession() {\n      return active ? session : undefined;\n    },\n    latestCapture() {\n      return latest;\n    },\n    get phaseCatalog() {\n      return phaseCatalog;\n    },\n  };\n}\n"]}