@replayablejs/devtools 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/disabled-stats-DrzL4cm1.js +12 -0
- package/dist/disabled-stats-DrzL4cm1.js.map +1 -0
- package/dist/end-card-trigger-q4ww_XP2.d.ts +8 -0
- package/dist/endcard-trigger/disabled.d.ts +7 -0
- package/dist/endcard-trigger/disabled.js +10 -0
- package/dist/endcard-trigger/disabled.js.map +1 -0
- package/dist/endcard-trigger/enabled.d.ts +7 -0
- package/dist/endcard-trigger/enabled.js +68 -0
- package/dist/endcard-trigger/enabled.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/install-devtools-interaction-1Flxw89C.js +61 -0
- package/dist/install-devtools-interaction-1Flxw89C.js.map +1 -0
- package/dist/sound-control/disabled.d.ts +7 -0
- package/dist/sound-control/disabled.js +10 -0
- package/dist/sound-control/disabled.js.map +1 -0
- package/dist/sound-control/enabled.d.ts +7 -0
- package/dist/sound-control/enabled.js +91 -0
- package/dist/sound-control/enabled.js.map +1 -0
- package/dist/sound-control-D8U9dP87.d.ts +9 -0
- package/dist/stats/disabled.d.ts +7 -0
- package/dist/stats/disabled.js +10 -0
- package/dist/stats/disabled.js.map +1 -0
- package/dist/stats/enabled.d.ts +7 -0
- package/dist/stats/enabled.js +1081 -0
- package/dist/stats/enabled.js.map +1 -0
- package/dist/stats/webgl/disabled.d.ts +6 -0
- package/dist/stats/webgl/disabled.js +10 -0
- package/dist/stats/webgl/disabled.js.map +1 -0
- package/dist/stats/webgl/enabled.d.ts +14 -0
- package/dist/stats/webgl/enabled.js +62 -0
- package/dist/stats/webgl/enabled.js.map +1 -0
- package/dist/stats-CMNxuMXo.d.ts +13 -0
- package/package.json +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enabled.js","names":["styles","noop","noop","noop","noop"],"sources":["../../src/stats/lifecycle/run-stats-cleanup.ts","../../src/stats/lifecycle/create-stats-lifecycle.ts","../../src/stats/measurement/create-stats-metrics.ts","../../src/stats/measurement/create-stats-sampling-clock.ts","../../src/stats/measurement/create-stats-sampler.ts","../../src/stats/presentation/create-stats-card-selection.ts","../../src/stats/presentation/create-stats-card-elements.ts","../../src/stats/presentation/create-stats-graph-elements.ts","../../src/stats/presentation/create-stats-graph.ts","../../src/stats/presentation/create-stats-card.ts","../../src/stats/presentation/create-stats-cards.ts","../../src/stats/presentation/stats.scss?inline","../../src/stats/presentation/create-stats-shell-elements.ts","../../src/stats/presentation/create-stats-shell.ts","../../src/stats/presentation/create-stats-view.ts","../../src/stats/measurement/create-stats-range.ts","../../src/stats/measurement/metrics/create-fps-metric.ts","../../src/stats/measurement/metrics/create-frame-interval-metric.ts","../../src/stats/measurement/metrics/create-js-heap-metric.ts","../../src/stats/webgl/observe-webgl-method.ts","../../src/stats/webgl/create-webgl-tracker.ts","../../src/stats/webgl/create-webgl-context-sampling.ts","../../src/stats/webgl/subscribe-webgl-frames.ts","../../src/stats/measurement/metrics/create-webgl-metric.ts","../../src/stats/stats-metrics.ts","../../src/create-stats.ts"],"sourcesContent":["/**\n * Attempts every cleanup in the supplied order, then reports failures.\n * Callers clear retained ownership before invoking this function, so a failed\n * cleanup cannot leave a stale subscription handle or block later destruction.\n * A single failure keeps its original identity; multiple failures are preserved.\n */\nexport function runStatsCleanup(actions: readonly (() => void)[]): void {\n const errors: unknown[] = [];\n for (const action of actions) {\n try {\n action();\n } catch (error) {\n errors.push(error);\n }\n }\n\n if (errors.length === 1) {\n throw errors[0];\n }\n if (errors.length > 1) {\n throw new AggregateError(errors, 'Failed to release every stats resource.');\n }\n}\n\n/** Preserves the setup failure even when rolling back acquired resources also fails. */\nexport function failStatsSetup(cause: unknown, cleanup: () => void): never {\n const errors = [cause];\n try {\n cleanup();\n } catch (cleanupError) {\n errors.push(cleanupError);\n }\n if (errors.length > 1) {\n throw new AggregateError(errors, 'Stats setup and cleanup both failed.', { cause });\n }\n throw cause;\n}\n","import { playable, type PostRenderContext } from '@replayablejs/runtime';\n\nimport type { StatsView } from '#types/presentation.js';\nimport type { StatsSampler } from '#types/sampling.js';\nimport type { Stats } from '#types/stats.js';\n\nimport { failStatsSetup, runStatsCleanup } from './run-stats-cleanup.js';\n\n/**\n * Connects measurement and presentation to Replayable's visibility and updates.\n * Host visibility never overrides manual hide intent. This owns every runtime\n * subscription and releases them together when the stats instance is destroyed.\n */\nexport function createStatsLifecycle(sampler: StatsSampler, view: StatsView): Stats {\n let hostVisible = playable.state.visible;\n let manuallyVisible = true;\n let destroyed = false;\n let removePostRenderListener: (() => void) | undefined;\n\n const removeVisibilityListener = playable.on('visibilitychange', handleVisibilityChange);\n try {\n updateVisibility();\n } catch (error) {\n // External instrumentation can fail; do not leave a mounted, half-started view.\n return failStatsSetup(error, destroy);\n }\n\n return { show, hide, destroy };\n\n /** Requests stats visibility without overriding the host's visibility state. */\n function show(): void {\n // Visibility intent can remain true after an automatic resume fails.\n // Only an active subscription means there is nothing left to start.\n if (destroyed || removePostRenderListener !== undefined) {\n return;\n }\n\n manuallyVisible = true;\n try {\n updateVisibility();\n } catch (error) {\n // Restore the caller's previous intent so show() can be retried directly.\n manuallyVisible = false;\n throw error;\n }\n }\n\n /** Releases stats' frame demand until explicitly shown again. */\n function hide(): void {\n if (destroyed || !manuallyVisible) {\n return;\n }\n\n manuallyVisible = false;\n updateVisibility();\n }\n\n /** Permanently releases subscriptions and DOM; later public calls are inert. */\n function destroy(): void {\n if (destroyed) {\n return;\n }\n\n destroyed = true;\n runStatsCleanup([removeVisibilityListener, stopSampling, () => view.destroy()]);\n }\n\n /** Applies host visibility without changing the caller's show/hide request. */\n function handleVisibilityChange(visible: boolean): void {\n if (destroyed) {\n return;\n }\n\n hostVisible = visible;\n updateVisibility();\n }\n\n /** Ignore callbacks retained in runtime's dispatch snapshot after stopSampling(). */\n function handlePostRender({ timestamp }: PostRenderContext): void {\n if (removePostRenderListener === undefined) {\n return;\n }\n\n const sample = sampler.update(timestamp);\n if (sample !== undefined) {\n view.update(sample);\n }\n }\n\n /** Stats run only when both the host and caller permit them. */\n function updateVisibility(): void {\n if (hostVisible && manuallyVisible) {\n startSampling();\n } else {\n stopSampling();\n }\n }\n\n /** Fresh measurements and history start together; lifetime extrema survive. */\n function startSampling(): void {\n if (removePostRenderListener !== undefined) {\n return;\n }\n\n try {\n sampler.reset(performance.now());\n // Sources acquire instrumentation only; runtime owns frame scheduling.\n sampler.start();\n view.clearHistory();\n view.show();\n removePostRenderListener = playable.postRender.add(handlePostRender);\n } catch (error) {\n return failStatsSetup(error, stopSampling);\n }\n }\n\n /** The unsubscribe handle is also the single source of subscription state. */\n function stopSampling(): void {\n const unsubscribe = removePostRenderListener;\n removePostRenderListener = undefined;\n runStatsCleanup([() => unsubscribe?.(), sampler.stop, () => view.hide()]);\n }\n}\n","import type { StatsMetricDefinition, StatsReading } from '#types/metric.js';\nimport type { StatsMetrics } from '#types/metrics.js';\nimport type { StatsSample } from '#types/sampling.js';\n\nimport { failStatsSetup, runStatsCleanup } from '../lifecycle/run-stats-cleanup.js';\n\n/** Creates independent measurement instances for the enabled registry definitions. */\nexport function createStatsMetrics(definitions: readonly StatsMetricDefinition[]): StatsMetrics {\n const metrics = definitions.map((definition) => ({\n key: definition.key,\n metric: definition.create(),\n }));\n\n return { start, stop, reset, measure, collect };\n\n /** CPU metrics need no subscription; external metrics acquire their shared source. */\n function start(): void {\n try {\n for (const { metric } of metrics) {\n metric.start();\n }\n } catch (error) {\n return failStatsSetup(error, stop);\n }\n }\n\n /** Release sources before discarding a partial window; lifetime ranges survive. */\n function stop(): void {\n runStatsCleanup(metrics.flatMap(({ metric }) => [() => metric.stop(), () => metric.reset()]));\n }\n\n /** Delegates window cleanup; the collection does not own individual metric state. */\n function reset(): void {\n for (const { metric } of metrics) {\n metric.reset();\n }\n }\n\n /** Every enabled metric receives the same real, unclamped frame interval. */\n function measure(deltaMilliseconds: number, frameTimestamp: number): void {\n for (const { metric } of metrics) {\n metric.measure(deltaMilliseconds, frameTimestamp);\n }\n }\n\n /** Preserves unavailable readings as undefined and leaves earlier snapshots untouched. */\n function collect(): StatsSample {\n const readings = new Map<StatsMetricDefinition['key'], StatsReading | undefined>();\n\n for (const { key, metric } of metrics) {\n readings.set(key, metric.collect());\n }\n\n return readings;\n }\n}\n","import type { StatsSamplingClock } from '#types/sampling-clock.js';\n\nconst REFRESH_INTERVAL_MILLISECONDS = 500;\n\n/** Delivers frames with real elapsed time (initially zero) and permits collection twice per second. */\nexport function createStatsSamplingClock(): StatsSamplingClock {\n let previousTimestamp: number | undefined;\n let nextRefreshTimestamp = 0;\n let hasFrameIntervals = false;\n\n return { reset, advance };\n\n /** The first subsequent frame establishes a baseline, excluding all hidden time. */\n function reset(timestamp: number): void {\n previousTimestamp = undefined;\n nextRefreshTimestamp = timestamp + REFRESH_INTERVAL_MILLISECONDS;\n hasFrameIntervals = false;\n }\n\n /** Delivers the frame before collection so the deadline-crossing frame belongs to this window. */\n function advance(timestamp: number, onFrame: (deltaMilliseconds: number) => void): boolean {\n advanceFrame(timestamp, onFrame);\n\n if (timestamp < nextRefreshTimestamp) {\n return false;\n }\n\n // A stall produces one refresh, not repeated catch-up samples of the same data.\n // Even an empty window advances the deadline while waiting for real intervals.\n nextRefreshTimestamp = timestamp + REFRESH_INTERVAL_MILLISECONDS;\n\n if (!hasFrameIntervals) {\n return false;\n }\n\n // Keep the frame baseline: the next interval starts at this frame, not after it.\n hasFrameIntervals = false;\n return true;\n }\n\n /** Delivers the initial frame with zero elapsed time; repeated timestamps are ignored. */\n function advanceFrame(timestamp: number, onFrame: (deltaMilliseconds: number) => void): void {\n const previous = previousTimestamp;\n previousTimestamp = timestamp;\n\n if (previous === undefined) {\n // Render counters still belong to this first frame. Interval metrics ignore\n // zero, and this baseline alone does not make a refresh window eligible.\n onFrame(0);\n return;\n }\n\n const deltaMilliseconds = timestamp - previous;\n\n if (deltaMilliseconds <= 0) {\n return;\n }\n\n hasFrameIntervals = true;\n onFrame(deltaMilliseconds);\n }\n}\n","import type { StatsMetricDefinition } from '#types/metric.js';\nimport type { StatsSample, StatsSampler } from '#types/sampling.js';\n\nimport { createStatsMetrics } from './create-stats-metrics.js';\nimport { createStatsSamplingClock } from './create-stats-sampling-clock.js';\n\n/** Dispatches real frame intervals to enabled metrics and collects them twice per second. */\nexport function createStatsSampler(definitions: readonly StatsMetricDefinition[]): StatsSampler {\n const metrics = createStatsMetrics(definitions);\n const clock = createStatsSamplingClock();\n\n return { start: metrics.start, stop: metrics.stop, reset, update };\n\n /** Discards partial measurements and excludes the visibility gap, preserving extrema. */\n function reset(timestamp: number): void {\n clock.reset(timestamp);\n\n metrics.reset();\n }\n\n /** The clock delivers this frame's interval before permitting metric collection. */\n function update(timestamp: number): StatsSample | undefined {\n const refresh = clock.advance(timestamp, (deltaMilliseconds) => {\n metrics.measure(deltaMilliseconds, timestamp);\n });\n if (!refresh) {\n return undefined;\n }\n\n return metrics.collect();\n }\n}\n","import type { StatsCardSelection } from '#types/card-selection.js';\nimport type { StatsCard } from '#types/presentation.js';\n\n/** Owns available-card selection and cycling; it never renders or changes measurements. */\nexport function createStatsCardSelection(cards: readonly StatsCard[]): StatsCardSelection {\n let current = cards.find((card) => card.available);\n\n return {\n get current(): StatsCard | undefined {\n return current;\n },\n resolve,\n cycle,\n };\n\n /** Preserves selection while available; an empty collection has no selected card. */\n function resolve(): void {\n if (!current?.available) {\n current = cards.find((card) => card.available);\n }\n }\n\n /** Skips unavailable cards and wraps without recording or clearing their history. */\n function cycle(): void {\n const available = cards.filter((card) => card.available);\n if (available.length === 0) {\n return;\n }\n\n const index = current === undefined ? -1 : available.indexOf(current);\n current = available[(index + 1) % available.length];\n }\n}\n","import type { StatsCardElements } from '#types/card-elements.js';\nimport type { StatsMetricDefinition } from '#types/metric.js';\n\n/** Builds an unmounted card with its graph behind the label, value, and range. */\nexport function createStatsCardElements(\n metric: StatsMetricDefinition,\n graph: SVGSVGElement,\n): StatsCardElements {\n const root = document.createElement('div');\n root.className = 'replayable-stats__card';\n root.dataset.label = metric.label;\n root.setAttribute('aria-label', metric.label);\n if (metric.description !== '') {\n root.title = metric.description;\n }\n\n const heading = document.createElement('span');\n heading.className = 'replayable-stats__label';\n heading.textContent = metric.label;\n\n const value = document.createElement('span');\n value.className = 'replayable-stats__value';\n value.textContent = '—';\n\n const range = document.createElement('span');\n range.className = 'replayable-stats__range';\n range.hidden = true;\n\n root.append(graph, heading, value, range);\n\n return { root, value, range };\n}\n","import type { StatsGraphElements } from '#types/graph-elements.js';\n\nconst SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n\n/** Creates an unmounted decorative SVG with its filled area behind the line. */\nexport function createStatsGraphElements(width: number, height: number): StatsGraphElements {\n const root = document.createElementNS(SVG_NAMESPACE, 'svg');\n root.setAttribute('viewBox', `0 0 ${width} ${height}`);\n root.setAttribute('aria-hidden', 'true');\n root.classList.add('replayable-stats__graph');\n\n const fill = document.createElementNS(SVG_NAMESPACE, 'path');\n fill.classList.add('replayable-stats__graph-area');\n\n const stroke = document.createElementNS(SVG_NAMESPACE, 'path');\n stroke.classList.add('replayable-stats__graph-line');\n stroke.setAttribute('fill', 'none');\n\n root.append(fill, stroke);\n\n return { root, fill, stroke };\n}\n","import { scaleLinear } from 'd3-scale';\nimport { area, line } from 'd3-shape';\n\nimport type { StatsGraph } from '#types/graph.js';\n\nimport { createStatsGraphElements } from './create-stats-graph-elements.js';\n\n/** Cards retain exactly as many refresh observations as the graph has X slots. */\nexport const STATS_HISTORY_LENGTH = 60;\n\nconst GRAPH_WIDTH = 96;\nconst GRAPH_HEIGHT = 56;\nconst GRAPH_INSET = 2;\n\n/**\n * Creates one reusable line and filled area. D3 only calculates\n * paths and coordinates; it does not manage DOM, transitions, or a frame loop.\n *\n * Observe samples before rendering. Keeping observation separate lets compact\n * cards remember peaks without redrawing hidden graphs on every refresh.\n */\nexport function createStatsGraph(initialCeiling: number): StatsGraph {\n const elements = createStatsGraphElements(GRAPH_WIDTH, GRAPH_HEIGHT);\n let ceiling = initialCeiling;\n\n // A short history occupies the rightmost slots instead of stretching across\n // the card. The newest sample always ends at the same right-hand position.\n const xScale = scaleLinear()\n .domain([0, STATS_HISTORY_LENGTH - 1])\n .range([GRAPH_INSET, GRAPH_WIDTH - GRAPH_INSET]);\n // SVG Y coordinates increase downward. Reverse the pixel range so zero sits\n // at the bottom and larger values rise toward the top, inside the graph inset.\n const yScale = scaleLinear()\n .domain([0, ceiling])\n .range([GRAPH_HEIGHT - GRAPH_INSET, GRAPH_INSET]);\n const linePath = line<number>().y((value) => yScale(value));\n const areaPath = area<number>()\n .y0(yScale(0))\n .y1((value) => yScale(value));\n\n return {\n element: elements.root,\n get ceiling(): number {\n return ceiling;\n },\n observe,\n render,\n };\n\n /** Grows the scale for new peaks without redrawing a potentially hidden graph. */\n function observe(value: number): void {\n // Grow by powers of two, never shrink. A disappearing spike or a cleared\n // history must not make ordinary values look larger on the next refresh.\n while (value > ceiling) {\n ceiling *= 2;\n }\n }\n\n /** Updates existing paths from recorded history; fewer than two samples draw no trace. */\n function render(history: readonly number[]): void {\n yScale.domain([0, ceiling]);\n if (history.length < 2) {\n elements.fill.removeAttribute('d');\n elements.stroke.removeAttribute('d');\n return;\n }\n\n const firstSlot = STATS_HISTORY_LENGTH - history.length;\n linePath.x((_value, index) => xScale(firstSlot + index));\n areaPath.x((_value, index) => xScale(firstSlot + index));\n // D3 defaults to straight segments. No smoothing that could invent peaks,\n // and no transitions that would add work or lag behind the observations.\n elements.stroke.setAttribute('d', linePath(history) ?? '');\n elements.fill.setAttribute('d', areaPath(history) ?? '');\n }\n}\n","import type { StatsMetricDefinition, StatsReading } from '#types/metric.js';\nimport type { StatsCard } from '#types/presentation.js';\nimport type { StatsRange } from '#types/range.js';\n\nimport { createStatsCardElements } from './create-stats-card-elements.js';\nimport { createStatsGraph, STATS_HISTORY_LENGTH } from './create-stats-graph.js';\n\n/** Owns one metric's recorded history and renders it into reusable card elements. */\nexport function createStatsCard(metric: StatsMetricDefinition): StatsCard {\n const graph = createStatsGraph(metric.ceiling);\n const elements = createStatsCardElements(metric, graph.element);\n const history: number[] = [];\n let isAvailable = metric.initiallyAvailable;\n let currentValue: number | undefined;\n let currentRange: StatsRange | undefined;\n\n return {\n key: metric.key,\n element: elements.root,\n label: metric.label,\n get available(): boolean {\n return isAvailable;\n },\n record,\n render,\n clearHistory,\n };\n\n /** Records data and graph peaks without redrawing, including while this card is hidden. */\n function record(reading: StatsReading | undefined): void {\n isAvailable = reading !== undefined;\n if (reading === undefined) {\n // An unavailable reading is not zero. Break the trace rather than connecting\n // observations across a reporting gap; keep the last lifetime range.\n clearHistory();\n return;\n }\n\n currentValue = reading.value;\n currentRange = reading.range;\n history.push(reading.graphValue);\n if (history.length > STATS_HISTORY_LENGTH) {\n history.shift();\n }\n graph.observe(reading.graphValue);\n }\n\n /** Draws stored values and history without recording another observation. */\n function render(): void {\n elements.value.textContent = currentValue?.toFixed(1) ?? '—';\n elements.range.hidden = currentRange === undefined;\n elements.range.textContent = formatRange(currentRange);\n graph.render(history);\n }\n\n /** Discards the trace and value, preserving availability, lifetime range, and graph scale. */\n function clearHistory(): void {\n history.length = 0;\n currentValue = undefined;\n }\n}\n\n/** Formats lifetime extrema as a compact label, leaving an unavailable range empty. */\nfunction formatRange(range: StatsRange | undefined): string {\n if (range === undefined) {\n return '';\n }\n\n return `(${Math.round(range.min)}–${Math.round(range.max)})`;\n}\n","import type { RuntimeStatsConfig } from '@replayablejs/runtime';\n\nimport type { StatsMetricDefinition } from '#types/metric.js';\nimport type { StatsCard, StatsCards } from '#types/presentation.js';\nimport type { StatsSample } from '#types/sampling.js';\n\nimport { createStatsCardSelection } from './create-stats-card-selection.js';\nimport { createStatsCard } from './create-stats-card.js';\n\n/** Owns card instances, recorded samples, and selection in registry order. */\nexport function createStatsCards(\n definitions: readonly StatsMetricDefinition[],\n display: RuntimeStatsConfig['display'],\n): StatsCards {\n const cards = definitions.map(createStatsCard);\n const selection = createStatsCardSelection(cards);\n const render = display === 'compact' ? renderCompact : renderExpanded;\n\n return {\n elements: cards.map((card) => card.element),\n get selectedLabel(): string | undefined {\n return selection.current?.label;\n },\n record,\n cycle: selection.cycle,\n render,\n clearHistory,\n };\n\n /** Hidden cards still receive samples so cycling never loses their history. */\n function record(sample: StatsSample): void {\n for (const card of cards) {\n card.record(sample.get(card.key));\n }\n }\n\n /** Displays only the selected available card; hidden cards retain their history. */\n function renderCompact(): void {\n selection.resolve();\n\n for (const card of cards) {\n renderCard(card, card === selection.current);\n }\n }\n\n /** Displays every available card without resolving compact selection. */\n function renderExpanded(): void {\n for (const card of cards) {\n renderCard(card, card.available);\n }\n }\n\n /** Clears traces, preserving availability, scales, and selection. */\n function clearHistory(): void {\n for (const card of cards) {\n card.clearHistory();\n }\n }\n\n /** Applies visibility and avoids redrawing hidden cards in either display mode. */\n function renderCard(card: StatsCard, visible: boolean): void {\n card.element.hidden = !visible;\n\n if (visible) {\n card.render();\n }\n }\n}\n",".replayable-stats {\n position: fixed;\n left: calc(2vmin + env(safe-area-inset-left, 0px));\n bottom: calc(2vmin + env(safe-area-inset-bottom, 0px));\n z-index: 2147483647;\n display: flex;\n flex-wrap: wrap;\n gap: 1vmin;\n max-width: calc(100% - 4vmin - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px));\n margin: 0;\n padding: 0;\n pointer-events: none;\n user-select: none;\n -webkit-user-select: none;\n font: 2.75vmin/1.4 ui-monospace, monospace;\n font-variant-numeric: tabular-nums;\n color: #e6f5ff;\n}\n.replayable-stats--compact[role=button] {\n pointer-events: auto;\n cursor: pointer;\n touch-action: none;\n -webkit-tap-highlight-color: transparent;\n}\n.replayable-stats--compact[role=button]:focus-visible {\n outline: 0.25vmin solid #a7bdd1;\n outline-offset: 0.5vmin;\n}\n.replayable-stats[hidden],\n.replayable-stats [hidden] {\n display: none;\n}\n.replayable-stats__card {\n position: relative;\n box-sizing: border-box;\n flex: 0 0 24vmin;\n width: 24vmin;\n height: 14vmin;\n overflow: hidden;\n padding: 1vmin 1.5vmin;\n border: 0.25vmin solid #435366;\n border-radius: 1vmin;\n background: rgba(12, 19, 28, 0.9);\n white-space: nowrap;\n}\n.replayable-stats__label, .replayable-stats__value {\n position: relative;\n display: block;\n text-shadow: 0 0.25vmin 0.5vmin #0c131c;\n}\n.replayable-stats__label {\n color: #a7bdd1;\n font-size: 2.5vmin;\n}\n.replayable-stats__value {\n position: absolute;\n left: 1.5vmin;\n bottom: 0.5vmin;\n font-size: 3vmin;\n}\n.replayable-stats__graph {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n}\n.replayable-stats__graph-area {\n fill: rgba(92, 190, 220, 0.12);\n}\n.replayable-stats__graph-line {\n stroke: rgba(112, 211, 239, 0.45);\n stroke-width: 1;\n}\n.replayable-stats__range {\n position: absolute;\n right: 0.5vmin;\n bottom: 0.5vmin;\n color: #8095a8;\n font-size: 1.75vmin;\n text-shadow: 0 0.25vmin 0.5vmin #0c131c;\n}","import type { RuntimeStatsConfig } from '@replayablejs/runtime';\n\nimport type { StatsShellElements } from '#types/shell-elements.js';\n\nimport styles from './stats.scss?inline';\n\n/** Builds the unmounted shell and stylesheet without installing input listeners. */\nexport function createStatsShellElements(\n cardElements: readonly HTMLElement[],\n display: RuntimeStatsConfig['display'],\n): StatsShellElements {\n const root = document.createElement('aside');\n root.className = 'replayable-stats';\n root.setAttribute('aria-label', 'Development stats');\n root.classList.toggle('replayable-stats--compact', display === 'compact');\n root.append(...cardElements);\n\n // Each shell owns its style node, so destroying one instance leaves others intact.\n const style = document.createElement('style');\n style.textContent = styles;\n\n return { root, style };\n}\n","import type { RuntimeStatsConfig } from '@replayablejs/runtime';\n\nimport type { StatsShell } from '#types/presentation.js';\n\nimport { installDevtoolsInteraction } from '../../interaction/install-devtools-interaction.js';\nimport { createStatsShellElements } from './create-stats-shell-elements.js';\n\n/** Mounts the shell, stylesheet, and optional compact input interception. */\nexport function createStatsShell(\n cardElements: readonly HTMLElement[],\n display: RuntimeStatsConfig['display'],\n onCycleCard: () => void,\n): StatsShell {\n const compact = display === 'compact';\n const { root, style } = createStatsShellElements(cardElements, display);\n const updateAccessibility = compact ? updateCompactAccessibility : noop;\n\n document.head.append(style);\n document.body.append(root);\n\n const removeInteraction = compact ? installDevtoolsInteraction(root, onCycleCard) : undefined;\n\n return { updateAccessibility, show, hide, destroy };\n\n /** Describes selection without leaving an empty view as an invisible click target. */\n function updateCompactAccessibility(label: string | undefined): void {\n if (label !== undefined) {\n root.setAttribute('role', 'button');\n root.tabIndex = 0;\n root.setAttribute('aria-label', `${label}. Show next stats card`);\n } else {\n root.removeAttribute('role');\n root.removeAttribute('tabindex');\n root.setAttribute('aria-label', 'Development stats');\n }\n }\n\n /** Visibility alone does not change card data or selection. */\n function show(): void {\n root.hidden = false;\n }\n\n /** Hidden shells do not intercept input. */\n function hide(): void {\n root.hidden = true;\n }\n\n /** Removes this shell's input listeners, DOM, and stylesheet. */\n function destroy(): void {\n removeInteraction?.();\n root.remove();\n style.remove();\n }\n}\n\n/** Expanded accessibility is static and already initialized by the shell elements. */\nfunction noop(): void {}\n","import type { RuntimeStatsConfig } from '@replayablejs/runtime';\n\nimport type { StatsMetricDefinition } from '#types/metric.js';\nimport type { StatsView } from '#types/presentation.js';\nimport type { StatsSample } from '#types/sampling.js';\n\nimport { createStatsCards } from './create-stats-cards.js';\nimport { createStatsShell } from './create-stats-shell.js';\n\n/** Coordinates metric cards and their mounted presentation shell. */\nexport function createStatsView(\n definitions: readonly StatsMetricDefinition[],\n display: RuntimeStatsConfig['display'],\n): StatsView {\n const cards = createStatsCards(definitions, display);\n const shell = createStatsShell(cards.elements, display, cycleCard);\n render();\n\n return { update, clearHistory, show: shell.show, hide: shell.hide, destroy };\n\n /** Records a refresh before redrawing the visible cards. */\n function update(sample: StatsSample): void {\n cards.record(sample);\n render();\n }\n\n /** Clears traces after a measurement gap without changing selection. */\n function clearHistory(): void {\n cards.clearHistory();\n render();\n }\n\n /** Changes the displayed card without recording another sample. */\n function cycleCard(): void {\n cards.cycle();\n render();\n }\n\n /** Keeps shell accessibility in sync with resolved card selection. */\n function render(): void {\n cards.render();\n shell.updateAccessibility(cards.selectedLabel);\n }\n\n /** Releases recorded histories and removes the shell without a final redraw. */\n function destroy(): void {\n shell.destroy();\n cards.clearHistory();\n }\n}\n","import type { StatsRange, StatsRangeTracker } from '#types/range.js';\n\n/** Tracks extrema without storing samples or allocating on each measured frame. */\nexport function createStatsRange(): StatsRangeTracker {\n let min = Infinity;\n let max = -Infinity;\n\n return {\n get current(): StatsRange | undefined {\n return min === Infinity ? undefined : { min, max };\n },\n observe(value): void {\n if (!Number.isFinite(value) || value < 0) {\n return;\n }\n min = Math.min(min, value);\n max = Math.max(max, value);\n },\n };\n}\n","import type { StatsMetric, StatsReading } from '#types/metric.js';\n\nimport { createStatsRange } from '../create-stats-range.js';\n\nconst MILLISECONDS_PER_SECOND = 1000;\n\n/** Counts complete frame intervals against real elapsed time, not instantaneous reciprocals. */\nexport function createFpsMetric(): StatsMetric {\n const range = createStatsRange();\n let intervalCount = 0;\n let totalMilliseconds = 0;\n\n return { start: noop, stop: noop, measure, collect, reset };\n\n function measure(deltaMilliseconds: number): void {\n // The initial render establishes timing but is not a complete frame interval.\n if (deltaMilliseconds === 0) {\n return;\n }\n intervalCount += 1;\n totalMilliseconds += deltaMilliseconds;\n }\n\n /** Lifetime extrema describe published FPS windows, not individual frames. */\n function collect(): StatsReading | undefined {\n if (intervalCount === 0) {\n return undefined;\n }\n const value = (intervalCount * MILLISECONDS_PER_SECOND) / totalMilliseconds;\n range.observe(value);\n reset();\n return { value, graphValue: value, range: range.current };\n }\n\n function reset(): void {\n intervalCount = 0;\n totalMilliseconds = 0;\n }\n}\n\n/** FPS uses supplied frame intervals and owns no external subscriptions. */\nfunction noop(): void {}\n","import type { StatsMetric, StatsReading } from '#types/metric.js';\n\nimport { createStatsRange } from '../create-stats-range.js';\n\n/** Displays the average, graphs the peak, and retains extrema of individual intervals. */\nexport function createFrameIntervalMetric(): StatsMetric {\n const range = createStatsRange();\n let intervalCount = 0;\n let totalMilliseconds = 0;\n let peakMilliseconds = 0;\n\n return { start: noop, stop: noop, measure, collect, reset };\n\n function measure(deltaMilliseconds: number): void {\n // Do not put the initial timing baseline into averages or lifetime extrema.\n if (deltaMilliseconds === 0) {\n return;\n }\n intervalCount += 1;\n totalMilliseconds += deltaMilliseconds;\n peakMilliseconds = Math.max(peakMilliseconds, deltaMilliseconds);\n // Include every real interval, even if hiding later discards this partial window.\n range.observe(deltaMilliseconds);\n }\n\n function collect(): StatsReading | undefined {\n if (intervalCount === 0) {\n return undefined;\n }\n const reading = {\n value: totalMilliseconds / intervalCount,\n graphValue: peakMilliseconds,\n range: range.current,\n };\n reset();\n return reading;\n }\n\n function reset(): void {\n intervalCount = 0;\n totalMilliseconds = 0;\n peakMilliseconds = 0;\n }\n}\n\n/** Frame intervals are supplied by runtime; no external resources need acquisition. */\nfunction noop(): void {}\n","import type { StatsMetric, StatsReading } from '#types/metric.js';\n\nimport { createStatsRange } from '../create-stats-range.js';\n\nconst BYTES_PER_MEGABYTE = 1_000_000;\n\n/** Reads browser memory only on refresh; it has no per-frame or partial-window work. */\nexport function createJsHeapMetric(): StatsMetric {\n const range = createStatsRange();\n return { start: noop, stop: noop, measure: noop, reset: noop, collect };\n\n function collect(): StatsReading | undefined {\n const value = readJsHeap();\n if (value === undefined) {\n return undefined;\n }\n range.observe(value);\n return { value, graphValue: value, range: range.current };\n }\n}\n\n/**\n * Reads the optional browser heap estimate only on collection.\n * This is not total playable memory: it excludes GPU allocations and may\n * include other work sharing the browser's JavaScript heap.\n */\nfunction readJsHeap(): number | undefined {\n if (!('memory' in performance)) {\n return undefined;\n }\n\n const { memory } = performance;\n\n if (typeof memory !== 'object' || memory === null || !('usedJSHeapSize' in memory)) {\n return undefined;\n }\n\n const bytes = memory.usedJSHeapSize;\n\n if (typeof bytes !== 'number' || !Number.isFinite(bytes) || bytes < 0) {\n return undefined;\n }\n\n return bytes / BYTES_PER_MEGABYTE;\n}\n\n/** Heap observations need no subscriptions, frame accumulation, or window reset. */\nfunction noop(): void {}\n","/**\n * Shadows a method on this context/extension only, leaving browser prototypes alone.\n * Observation happens after a normal return: thrown calls retain their exception\n * and are not counted. WebGL error flags are deliberately never queried.\n */\nexport function observeWebglMethod(\n target: object,\n name: string,\n observe: (args: readonly unknown[]) => void,\n): () => void {\n const original: unknown = Reflect.get(target, name);\n if (typeof original !== 'function') {\n return noop;\n }\n const method = original;\n\n const descriptor = Object.getOwnPropertyDescriptor(target, name);\n let active = true;\n\n // Restoring the exact descriptor also preserves pre-existing instrumentation.\n Object.defineProperty(target, name, {\n configurable: true,\n enumerable: descriptor?.enumerable ?? false,\n writable: true,\n value: observedMethod,\n });\n\n return restore;\n\n /** Preserve the receiver, argument identities, return value, and native exceptions. */\n function observedMethod(this: unknown, ...args: unknown[]): unknown {\n const result: unknown = Reflect.apply(method, this, args);\n if (active) {\n observe(args);\n }\n return result;\n }\n\n /** Never overwrite a wrapper installed by another tool after ours. */\n function restore(): void {\n if (!active) {\n return;\n }\n active = false;\n if (Object.getOwnPropertyDescriptor(target, name)?.value !== observedMethod) {\n return;\n }\n if (descriptor === undefined) {\n if (!Reflect.deleteProperty(target, name)) {\n throw new TypeError(`Unable to remove the stats wrapper for ${name}.`);\n }\n } else {\n Object.defineProperty(target, name, descriptor);\n }\n }\n}\n\n/** Missing WebGL2/extension methods need no instrumentation or cleanup. */\nfunction noop(): void {}\n","import type { StatsWebglContext, StatsWebglCounts, StatsWebglTracker } from '#types/webgl.js';\n\nimport { failStatsSetup, runStatsCleanup } from '../lifecycle/run-stats-cleanup.js';\nimport { observeWebglMethod } from './observe-webgl-method.js';\n\nconst trackers = new WeakMap<StatsWebglContext, StatsWebglTracker>();\n\n/**\n * Instruments one renderer-created context, reusing it when renderers share it.\n * No scheduling, context creation, GPU queries, or renderer knowledge lives here.\n * The later sampling lifecycle owns this tracker and calls collect after rendering.\n */\nexport function createWebglTracker(context: StatsWebglContext): StatsWebglTracker {\n const existing = trackers.get(context);\n if (existing !== undefined) {\n return existing;\n }\n\n let drawCalls = 0;\n let textureBinds = 0;\n let programUses = 0;\n const restoreMethods: (() => void)[] = [];\n const tracker: StatsWebglTracker = { collect, reset, destroy };\n\n // A failed installation must not leave a partly patched rendering context.\n try {\n observeDrawMethods();\n observe(context, 'bindTexture', recordTextureBind);\n observe(context, 'useProgram', recordProgramUse);\n observeExtensions();\n } catch (error) {\n return failStatsSetup(error, destroy);\n }\n\n trackers.set(context, tracker);\n return tracker;\n\n /** Instanced submissions count once, regardless of how many instances they draw. */\n function observeDrawMethods(): void {\n for (const name of [\n 'drawArrays',\n 'drawElements',\n 'drawRangeElements',\n 'drawArraysInstanced',\n 'drawElementsInstanced',\n ]) {\n observe(context, name, recordDraw);\n }\n }\n\n /**\n * Retrieve extension objects once so already-cached objects are instrumented too.\n * This enables supported extensions, but never creates a WebGL context. Native\n * extension objects are reused by getExtension; unsupported ones return null.\n * A function copied/bound before installation cannot be intercepted retroactively.\n * https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_multi_draw\n */\n function observeExtensions(): void {\n const instancing: unknown = context.getExtension('ANGLE_instanced_arrays');\n if (typeof instancing === 'object' && instancing !== null) {\n observe(instancing, 'drawArraysInstancedANGLE', recordDraw);\n observe(instancing, 'drawElementsInstancedANGLE', recordDraw);\n }\n\n const multiDraw: unknown = context.getExtension('WEBGL_multi_draw');\n if (typeof multiDraw === 'object' && multiDraw !== null) {\n observe(multiDraw, 'multiDrawArraysWEBGL', (args) => recordMultiDraw(args[5]));\n observe(multiDraw, 'multiDrawElementsWEBGL', (args) => recordMultiDraw(args[6]));\n observe(multiDraw, 'multiDrawArraysInstancedWEBGL', (args) => recordMultiDraw(args[7]));\n observe(multiDraw, 'multiDrawElementsInstancedWEBGL', (args) => recordMultiDraw(args[8]));\n }\n }\n\n /** Keeps installation and cleanup paired for both context and extension methods. */\n function observe(target: object, name: string, record: (args: readonly unknown[]) => void): void {\n restoreMethods.push(observeWebglMethod(target, name, record));\n }\n\n /** One ordinary API submission, including zero-count draws; no GPU success claim. */\n function recordDraw(): void {\n drawCalls += 1;\n }\n\n /** Reads the signature's drawcount, ignoring extra arguments as WebGL does. */\n function recordMultiDraw(count: unknown): void {\n // Do not coerce user objects a second time or let invalid counts poison stats.\n if (typeof count === 'number' && Number.isInteger(count) && count > 0) {\n drawCalls += count;\n }\n }\n\n /** Repeated bindings and null unbindings are still API calls. */\n function recordTextureBind(): void {\n textureBinds += 1;\n }\n\n /** Counts useProgram calls, not unique programs or successful shader switches. */\n function recordProgramUse(): void {\n programUses += 1;\n }\n\n /** Allocate only at the caller's sampling boundary, never a snapshot per GL call. */\n function collect(): StatsWebglCounts {\n const counts = { drawCalls, textureBinds, programUses };\n reset();\n return counts;\n }\n\n /** Discards a partial frame without changing the installed wrappers. */\n function reset(): void {\n drawCalls = 0;\n textureBinds = 0;\n programUses = 0;\n }\n\n /** Idempotent cleanup; stale owners cannot remove a newer tracker. */\n function destroy(): void {\n // Drain ownership first: even an unrestorable method must not prevent the\n // other wrappers from being disabled, or retain this tracker in the cache.\n const restores = restoreMethods.splice(0).reverse();\n reset();\n if (trackers.get(context) === tracker) {\n trackers.delete(context);\n }\n runStatsCleanup(restores);\n }\n}\n","import type {\n StatsWebglContext,\n StatsWebglContextSampling,\n StatsWebglCounts,\n StatsWebglTracker,\n} from '#types/webgl.js';\n\nimport { failStatsSetup, runStatsCleanup } from '../lifecycle/run-stats-cleanup.js';\nimport { createWebglTracker } from './create-webgl-tracker.js';\n\n/** Owns instrumentation and loss listeners for one registered, renderer-created context. */\nexport function createWebglContextSampling(context: StatsWebglContext): StatsWebglContextSampling {\n let tracker: StatsWebglTracker | undefined;\n let destroyed = false;\n\n try {\n context.canvas.addEventListener('webglcontextlost', handleContextLost);\n context.canvas.addEventListener('webglcontextrestored', handleContextRestored);\n handleContextRestored();\n } catch (error) {\n return failStatsSetup(error, destroy);\n }\n\n return { collect, destroy };\n\n /** Lost contexts are unavailable, not zero-work frames. */\n function collect(): StatsWebglCounts | undefined {\n return tracker?.collect();\n }\n\n /** Leave preventDefault/restoration policy to the renderer; discard partial counts. */\n function handleContextLost(): void {\n const previousTracker = tracker;\n tracker = undefined;\n previousTracker?.destroy();\n }\n\n /** Reacquire extension objects and wrappers after restoration, with fresh counters. */\n function handleContextRestored(): void {\n if (!destroyed && tracker === undefined && !context.isContextLost()) {\n tracker = createWebglTracker(context);\n }\n }\n\n /** Removes listeners before releasing wrappers; does not destroy the canvas or GL context. */\n function destroy(): void {\n if (destroyed) {\n return;\n }\n destroyed = true;\n runStatsCleanup([\n () => context.canvas.removeEventListener('webglcontextlost', handleContextLost),\n () => context.canvas.removeEventListener('webglcontextrestored', handleContextRestored),\n handleContextLost,\n ]);\n }\n}\n","import type {\n StatsWebglContext,\n StatsWebglContextSampling,\n StatsWebglFrameListener,\n} from '#types/webgl.js';\n\nimport { failStatsSetup, runStatsCleanup } from '../lifecycle/run-stats-cleanup.js';\nimport { subscribeWebglContexts } from './context-registry.js';\nimport { createWebglContextSampling } from './create-webgl-context-sampling.js';\n\n// All cards/stats instances share one frame snapshot. Collecting separately\n// would reset counters before the other consumers could read the same frame.\nconst listeners = new Set<StatsWebglFrameListener>();\nconst contexts = new Map<StatsWebglContext, StatsWebglContextSampling>();\nlet collectedTimestamp: number | undefined;\nlet removeContextListener: (() => void) | undefined;\n\n/**\n * Acquires frame sampling until the returned release is called (hide/destroy).\n * The first consumer installs instrumentation; the last release restores it.\n * Stats lifecycle releases consumers when hidden; this source owns no visibility policy.\n * Merely importing this module allocates no DOM, wrappers, or subscriptions.\n */\nexport function subscribeWebglFrames(listener: StatsWebglFrameListener): () => void {\n // Each subscription owns a distinct identity, even when callbacks are shared.\n const deliver: StatsWebglFrameListener = (timestamp, counts) => listener(timestamp, counts);\n listeners.add(deliver);\n if (listeners.size === 1) {\n try {\n startSampling();\n } catch (error) {\n return failStatsSetup(error, release);\n }\n }\n return release;\n\n /** Idempotent release; other visible stats instances keep their shared tracker. */\n function release(): void {\n if (!listeners.delete(deliver) || listeners.size !== 0) {\n return;\n }\n stopSampling();\n }\n}\n\n/** Replay existing contexts before the next update, or observe later registrations. */\nfunction startSampling(): void {\n if (removeContextListener !== undefined) {\n return;\n }\n removeContextListener = subscribeWebglContexts(updateContexts);\n}\n\n/** Release every owned wrapper and context reference, invalidating the previous frame. */\nfunction stopSampling(): void {\n collectedTimestamp = undefined;\n const unsubscribe = removeContextListener;\n removeContextListener = undefined;\n const samplings = [...contexts.values()];\n contexts.clear();\n runStatsCleanup([() => unsubscribe?.(), ...samplings.map((sampling) => sampling.destroy)]);\n}\n\n/** Registry snapshots already deduplicate contexts shared by Pixi and Three. */\nfunction updateContexts(registered: readonly StatsWebglContext[]): void {\n const removals: (() => void)[] = [];\n for (const [context, sampling] of contexts) {\n if (!registered.includes(context)) {\n contexts.delete(context);\n removals.push(sampling.destroy);\n }\n }\n runStatsCleanup(removals);\n\n for (const context of registered) {\n if (!contexts.has(context)) {\n contexts.set(context, createWebglContextSampling(context));\n }\n }\n}\n\n/**\n * Called by metric sampling after rendering, never by an independent scheduler.\n * Motion's frame timestamp is shared across stats instances, unlike their local\n * performance.now() readings. It makes collection/reset happen once per frame.\n * Delivery updates all subscribed metrics before any instance collects its readings.\n */\nexport function collectWebglFrame(timestamp: number): void {\n if (removeContextListener === undefined || collectedTimestamp === timestamp) {\n return;\n }\n collectedTimestamp = timestamp;\n let available = false;\n let drawCalls = 0;\n let textureBinds = 0;\n let programUses = 0;\n for (const sampling of contexts.values()) {\n const counts = sampling.collect();\n if (counts !== undefined) {\n available = true;\n drawCalls += counts.drawCalls;\n textureBinds += counts.textureBinds;\n programUses += counts.programUses;\n }\n }\n const counts = available ? Object.freeze({ drawCalls, textureBinds, programUses }) : undefined;\n for (const listener of [...listeners]) {\n // A preceding callback can hide/destroy another stats instance during delivery.\n if (listeners.has(listener)) {\n listener(timestamp, counts);\n }\n }\n}\n","import type { StatsMetric, StatsReading } from '#types/metric.js';\nimport type { StatsWebglCounts } from '#types/webgl.js';\n\nimport { collectWebglFrame, subscribeWebglFrames } from '../../webgl/subscribe-webgl-frames.js';\nimport { createStatsRange } from '../create-stats-range.js';\n\n/** One counter's frame average, graph peak, and lifetime per-frame extrema. */\nexport function createWebglMetric(key: keyof StatsWebglCounts): StatsMetric {\n const range = createStatsRange();\n let unsubscribeFromFrames: (() => void) | undefined;\n let frames = 0;\n let total = 0;\n let peak = 0;\n\n return { start, stop, measure, collect, reset };\n\n /** The source collects once per frame even when all three metrics subscribe. */\n function start(): void {\n unsubscribeFromFrames ??= subscribeWebglFrames(recordFrame);\n }\n\n /** Releases frame sampling and clears this metric's subscription state. */\n function stop(): void {\n const unsubscribe = unsubscribeFromFrames;\n\n // Clear ownership before invoking cleanup. Cleanup may throw or re-enter\n // stop(); neither case should leave a stale handle or unsubscribe twice.\n // A later start() can then acquire a fresh subscription.\n unsubscribeFromFrames = undefined;\n\n unsubscribe?.();\n }\n\n /** All metrics use the same post-render measurement path; the source deduplicates frames. */\n function measure(_deltaMilliseconds: number, frameTimestamp: number): void {\n collectWebglFrame(frameTimestamp);\n }\n\n /** A missing/lost context invalidates this window rather than publishing stale work. */\n function recordFrame(_timestamp: number, counts: StatsWebglCounts | undefined): void {\n if (counts === undefined) {\n reset();\n return;\n }\n const count = counts[key];\n frames += 1;\n total += count;\n peak = Math.max(peak, count);\n range.observe(count);\n }\n\n /** Average includes idle zero-work frames; graph and extrema retain individual peaks. */\n function collect(): StatsReading | undefined {\n if (frames === 0) {\n return undefined;\n }\n const reading = { value: total / frames, graphValue: peak, range: range.current };\n reset();\n return reading;\n }\n\n /** Discard partial counts without clearing the metric's lifetime range. */\n function reset(): void {\n frames = 0;\n total = 0;\n peak = 0;\n }\n}\n","import type { StatsMetricDefinition } from '#types/metric.js';\n\nimport { createFpsMetric } from './measurement/metrics/create-fps-metric.js';\nimport { createFrameIntervalMetric } from './measurement/metrics/create-frame-interval-metric.js';\nimport { createJsHeapMetric } from './measurement/metrics/create-js-heap-metric.js';\nimport { createWebglMetric } from './measurement/metrics/create-webgl-metric.js';\n\n/**\n * The single built-in registry: order, presentation, and measurement factory.\n * Add a metric implementation here; sampler, lifecycle, and cards need no edits.\n * A new authored toggle also belongs in config's strict stats schema and runtime type.\n */\nexport const STATS_METRICS: readonly StatsMetricDefinition[] = [\n {\n key: 'fps',\n label: 'FPS',\n description: '',\n ceiling: 120,\n initiallyAvailable: true,\n create: createFpsMetric,\n },\n {\n key: 'frameInterval',\n label: 'Frame · ms',\n description: '',\n ceiling: 100,\n initiallyAvailable: true,\n create: createFrameIntervalMetric,\n },\n {\n key: 'jsHeap',\n label: 'JS heap · MB',\n description: 'Approximate browser-reported JS heap; not total playable memory.',\n ceiling: 64,\n initiallyAvailable: false,\n create: createJsHeapMetric,\n },\n {\n key: 'drawCalls',\n label: 'Draw calls',\n description:\n 'Submitted draws per frame; instanced draws count once, multi-draw counts each draw.',\n ceiling: 100,\n initiallyAvailable: false,\n create: () => createWebglMetric('drawCalls'),\n },\n {\n key: 'textureBinds',\n label: 'Texture binds',\n description: 'bindTexture calls per frame, including repeated bindings and null unbindings.',\n ceiling: 100,\n initiallyAvailable: false,\n create: () => createWebglMetric('textureBinds'),\n },\n {\n key: 'programUses',\n label: 'Program uses',\n description: 'useProgram calls per frame, not unique programs or shaders.',\n ceiling: 100,\n initiallyAvailable: false,\n create: () => createWebglMetric('programUses'),\n },\n];\n","import { playable } from '@replayablejs/runtime';\n\nimport type { Stats } from '#types/stats.js';\n\nimport { disabledStats } from './stats/disabled-stats.js';\nimport { createStatsLifecycle } from './stats/lifecycle/create-stats-lifecycle.js';\nimport { createStatsSampler } from './stats/measurement/create-stats-sampler.js';\nimport { createStatsView } from './stats/presentation/create-stats-view.js';\nimport { STATS_METRICS } from './stats/stats-metrics.js';\n\n/** Creates optional development stats after await playable.ready(). */\nexport function createStats(): Stats {\n const config = playable.config.devtools.stats;\n // The pipeline selects this entry only for enabled stats. Keep the guard for\n // the runtime configuration's false | settings type and safe direct use.\n if (config === false) {\n return disabledStats;\n }\n\n const metrics = STATS_METRICS.filter((metric) => config[metric.key]);\n if (metrics.length === 0) {\n return disabledStats;\n }\n\n requireRuntimeReady();\n\n const sampler = createStatsSampler(metrics);\n const view = createStatsView(metrics, config.display);\n\n return createStatsLifecycle(sampler, view);\n}\n\n/** Requires await playable.ready() before stats can allocate DOM or subscribe. */\nfunction requireRuntimeReady(): void {\n // Runtime exposes state only after initialization; its getter owns the error.\n void playable.state;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/stats/webgl/disabled.d.ts
|
|
2
|
+
/** Disabled registration retains no context and installs no listeners or wrappers. */
|
|
3
|
+
declare function registerWebglContext(): () => void;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { registerWebglContext };
|
|
6
|
+
//# sourceMappingURL=disabled.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/stats/webgl/disabled.ts
|
|
2
|
+
/** Disabled registration retains no context and installs no listeners or wrappers. */
|
|
3
|
+
function registerWebglContext() {
|
|
4
|
+
return noop;
|
|
5
|
+
}
|
|
6
|
+
function noop() {}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { registerWebglContext };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=disabled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disabled.js","names":[],"sources":["../../../src/stats/webgl/disabled.ts"],"sourcesContent":["/** Disabled registration retains no context and installs no listeners or wrappers. */\nexport function registerWebglContext(): () => void {\n return noop;\n}\n\nfunction noop(): void {}\n"],"mappings":";;AACA,SAAgB,uBAAmC;CACjD,OAAO;AACT;AAEA,SAAS,OAAa,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/types/webgl.d.ts
|
|
2
|
+
/** Renderer-created context identity; devtools never creates a context. */
|
|
3
|
+
type StatsWebglContext = WebGLRenderingContext | WebGL2RenderingContext;
|
|
4
|
+
/** Receives the current registered contexts immediately and after registration changes. */
|
|
5
|
+
type StatsWebglContextListener = (contexts: readonly StatsWebglContext[]) => void;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/stats/webgl/context-registry.d.ts
|
|
8
|
+
/** Registers an existing context and returns an idempotent release for this owner. */
|
|
9
|
+
declare function registerWebglContext(context: StatsWebglContext): () => void;
|
|
10
|
+
/** Late observers receive existing contexts; early observers receive later registrations. */
|
|
11
|
+
declare function subscribeWebglContexts(listener: StatsWebglContextListener): () => void;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { registerWebglContext, subscribeWebglContexts };
|
|
14
|
+
//# sourceMappingURL=enabled.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/stats/webgl/context-registry.ts
|
|
2
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
3
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
/** Registers an existing context and returns an idempotent release for this owner. */
|
|
5
|
+
function registerWebglContext(context) {
|
|
6
|
+
const count = registrations.get(context) ?? 0;
|
|
7
|
+
registrations.set(context, count + 1);
|
|
8
|
+
if (count === 0) {
|
|
9
|
+
const errors = notifyListeners();
|
|
10
|
+
if (errors.length > 0) {
|
|
11
|
+
registrations.delete(context);
|
|
12
|
+
errors.push(...notifyListeners());
|
|
13
|
+
throw new AggregateError(errors, "Failed to register the WebGL context.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let released = false;
|
|
17
|
+
return unregister;
|
|
18
|
+
/** Removes the context only after its last owner releases it. */
|
|
19
|
+
function unregister() {
|
|
20
|
+
if (released) return;
|
|
21
|
+
released = true;
|
|
22
|
+
const remaining = (registrations.get(context) ?? 1) - 1;
|
|
23
|
+
if (remaining > 0) registrations.set(context, remaining);
|
|
24
|
+
else {
|
|
25
|
+
registrations.delete(context);
|
|
26
|
+
const errors = notifyListeners();
|
|
27
|
+
if (errors.length > 0) throw new AggregateError(errors, "Failed to notify WebGL context removal.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Late observers receive existing contexts; early observers receive later registrations. */
|
|
32
|
+
function subscribeWebglContexts(listener) {
|
|
33
|
+
listeners.add(listener);
|
|
34
|
+
try {
|
|
35
|
+
listener(contextSnapshot());
|
|
36
|
+
} catch (error) {
|
|
37
|
+
listeners.delete(listener);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
return () => {
|
|
41
|
+
listeners.delete(listener);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Notify every observer, even if one fails, so cleanup never stops halfway through. */
|
|
45
|
+
function notifyListeners() {
|
|
46
|
+
const contexts = contextSnapshot();
|
|
47
|
+
const errors = [];
|
|
48
|
+
for (const listener of [...listeners]) try {
|
|
49
|
+
listener(contexts);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
errors.push(error);
|
|
52
|
+
}
|
|
53
|
+
return errors;
|
|
54
|
+
}
|
|
55
|
+
/** Frozen snapshots prevent one observer from changing another observer's context list. */
|
|
56
|
+
function contextSnapshot() {
|
|
57
|
+
return Object.freeze([...registrations.keys()]);
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { registerWebglContext, subscribeWebglContexts };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=enabled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enabled.js","names":[],"sources":["../../../src/stats/webgl/context-registry.ts"],"sourcesContent":["import type { StatsWebglContext, StatsWebglContextListener } from '#types/webgl.js';\n\n// Multiple owners may register the same context, but observers see it only once.\nconst registrations = new Map<StatsWebglContext, number>();\nconst listeners = new Set<StatsWebglContextListener>();\n\n/** Registers an existing context and returns an idempotent release for this owner. */\nexport function registerWebglContext(context: StatsWebglContext): () => void {\n const count = registrations.get(context) ?? 0;\n registrations.set(context, count + 1);\n if (count === 0) {\n const errors = notifyListeners();\n if (errors.length > 0) {\n // No release handle reached the caller. Restore the registry and let\n // observers undo any instrumentation installed before another one failed.\n registrations.delete(context);\n errors.push(...notifyListeners());\n throw new AggregateError(errors, 'Failed to register the WebGL context.');\n }\n }\n\n let released = false;\n return unregister;\n\n /** Removes the context only after its last owner releases it. */\n function unregister(): void {\n if (released) {\n return;\n }\n released = true;\n\n const remaining = (registrations.get(context) ?? 1) - 1;\n if (remaining > 0) {\n registrations.set(context, remaining);\n } else {\n registrations.delete(context);\n const errors = notifyListeners();\n if (errors.length > 0) {\n throw new AggregateError(errors, 'Failed to notify WebGL context removal.');\n }\n }\n }\n}\n\n/** Late observers receive existing contexts; early observers receive later registrations. */\nexport function subscribeWebglContexts(listener: StatsWebglContextListener): () => void {\n listeners.add(listener);\n try {\n listener(contextSnapshot());\n } catch (error) {\n listeners.delete(listener);\n throw error;\n }\n\n return (): void => {\n listeners.delete(listener);\n };\n}\n\n/** Notify every observer, even if one fails, so cleanup never stops halfway through. */\nfunction notifyListeners(): unknown[] {\n const contexts = contextSnapshot();\n const errors: unknown[] = [];\n for (const listener of [...listeners]) {\n try {\n listener(contexts);\n } catch (error) {\n errors.push(error);\n }\n }\n return errors;\n}\n\n/** Frozen snapshots prevent one observer from changing another observer's context list. */\nfunction contextSnapshot(): readonly StatsWebglContext[] {\n return Object.freeze([...registrations.keys()]);\n}\n"],"mappings":";AAGA,MAAM,gCAAgB,IAAI,IAA+B;AACzD,MAAM,4BAAY,IAAI,IAA+B;;AAGrD,SAAgB,qBAAqB,SAAwC;CAC3E,MAAM,QAAQ,cAAc,IAAI,OAAO,KAAK;CAC5C,cAAc,IAAI,SAAS,QAAQ,CAAC;CACpC,IAAI,UAAU,GAAG;EACf,MAAM,SAAS,gBAAgB;EAC/B,IAAI,OAAO,SAAS,GAAG;GAGrB,cAAc,OAAO,OAAO;GAC5B,OAAO,KAAK,GAAG,gBAAgB,CAAC;GAChC,MAAM,IAAI,eAAe,QAAQ,uCAAuC;EAC1E;CACF;CAEA,IAAI,WAAW;CACf,OAAO;;CAGP,SAAS,aAAmB;EAC1B,IAAI,UACF;EAEF,WAAW;EAEX,MAAM,aAAa,cAAc,IAAI,OAAO,KAAK,KAAK;EACtD,IAAI,YAAY,GACd,cAAc,IAAI,SAAS,SAAS;OAC/B;GACL,cAAc,OAAO,OAAO;GAC5B,MAAM,SAAS,gBAAgB;GAC/B,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,eAAe,QAAQ,yCAAyC;EAE9E;CACF;AACF;;AAGA,SAAgB,uBAAuB,UAAiD;CACtF,UAAU,IAAI,QAAQ;CACtB,IAAI;EACF,SAAS,gBAAgB,CAAC;CAC5B,SAAS,OAAO;EACd,UAAU,OAAO,QAAQ;EACzB,MAAM;CACR;CAEA,aAAmB;EACjB,UAAU,OAAO,QAAQ;CAC3B;AACF;;AAGA,SAAS,kBAA6B;CACpC,MAAM,WAAW,gBAAgB;CACjC,MAAM,SAAoB,CAAC;CAC3B,KAAK,MAAM,YAAY,CAAC,GAAG,SAAS,GAClC,IAAI;EACF,SAAS,QAAQ;CACnB,SAAS,OAAO;EACd,OAAO,KAAK,KAAK;CACnB;CAEF,OAAO;AACT;;AAGA,SAAS,kBAAgD;CACvD,OAAO,OAAO,OAAO,CAAC,GAAG,cAAc,KAAK,CAAC,CAAC;AAChD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/types/stats.d.ts
|
|
2
|
+
/** Lifecycle controls for the optional development stats shell. */
|
|
3
|
+
interface Stats {
|
|
4
|
+
/** Shows the shell and resumes measurements; inert after destruction. */
|
|
5
|
+
show(): void;
|
|
6
|
+
/** Hides the shell and unsubscribes from measurements. */
|
|
7
|
+
hide(): void;
|
|
8
|
+
/** Permanently releases the shell and its subscriptions. Safe to call again. */
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { Stats as t };
|
|
13
|
+
//# sourceMappingURL=stats-CMNxuMXo.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@replayablejs/devtools",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Renderer-independent development tools for Replayable playables",
|
|
5
|
+
"homepage": "https://github.com/replayablejs/replayable#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/replayablejs/replayable/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/replayablejs/replayable.git",
|
|
13
|
+
"directory": "packages/devtools"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"imports": {
|
|
21
|
+
"#types/*": "./src/types/*"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"d3-scale": "4.0.2",
|
|
34
|
+
"d3-shape": "3.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@tsdown/css": "0.22.14",
|
|
38
|
+
"@types/d3-scale": "4.0.9",
|
|
39
|
+
"@types/d3-shape": "3.2.0",
|
|
40
|
+
"@types/node": "26.2.0",
|
|
41
|
+
"happy-dom": "20.13.2",
|
|
42
|
+
"sass-embedded": "1.103.1",
|
|
43
|
+
"tsdown": "0.22.14",
|
|
44
|
+
"typescript": "7.0.2",
|
|
45
|
+
"vite": "8.2.1",
|
|
46
|
+
"vitest": "4.1.10",
|
|
47
|
+
"@replayablejs/runtime": "0.1.0-alpha.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@replayablejs/runtime": "0.1.0-alpha.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=24.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsdown",
|
|
57
|
+
"dev": "tsdown --watch",
|
|
58
|
+
"lint": "oxlint --type-aware --max-warnings 0 .",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
61
|
+
}
|
|
62
|
+
}
|