import { RegistryData } from "../ecs/batchUtils.js"; import { BatchesPayload } from "../debug-protocol.js"; import { BatchSourceFn, MeshBatchSourceFn } from "./debug-sink.js"; import { InstancedMesh } from "three"; import { WebGPURenderer } from "three/webgpu"; //#region src/debug/BatchCollector.d.ts /** * Producer-side scratch collector for per-frame render-pass events and * the current `BatchRegistry` snapshot. Feeds the `'batches'` devtools * feature. * * **Timing model — mirrors `StatsCollector`.** The scratch exposed to * `drain()` only ever contains data from a FULLY COMPLETED frame. The * in-progress frame writes into a separate "building" pool; at the * end of `DevtoolsProvider.endFrame` a single `commit()` call swaps * the pools by pointer flip and bumps a version counter. A flush * landing between two frames sees the last committed snapshot; a * flush landing during a rAF's work sees the previous frame's * snapshot — never half-built state. * * Ordering each frame: * 1. `beginFrame` — reset build counts to zero (pointers untouched; * the previous frame's data remains readable on the published * pool until `commit` swaps). * 2. `frameStart` — optional; captures renderer-info baseline and * stakes the root "frame" pass at index 0 of the BUILD pool. * 3. `beginPass` / `endPass` — write into the build pool, never * touching the published pool. * 4. `frameEnd` — populate the root pass's totals from the full * frame's renderer-info delta. * 5. `captureAllSources` — walk every registered `RegistryData` * source and fill the build batch pool. * 6. `commit` — swap build ↔ published, increment version. * * Drain compares `_version` to `_lastEmittedVersion`: * - Advanced → ship the published pool. Update `_lastEmittedVersion`. * - Unchanged → `features.batches` stays absent on the wire, * which the protocol interprets as "no change" per the delta * semantics in `debug-protocol.ts`. * * `resetDelta` just rewinds `_lastEmittedVersion` so a re-subscribing * consumer gets the current snapshot on the next flush — or an empty * one if no frame has committed yet. * * - **Zero per-frame allocation past warmup.** Pools grow on demand * and are reused in place; pointer swap is two field assignments. * - **Zero cost when inactive.** `_capturing = false` short-circuits * every public entry so the build pool never grows and * `performance.now` calls are skipped. */ declare class BatchCollector { /** * Two pre-allocated pool pairs — one "build" (mutated during the * current frame), one "published" (read by `drain`). `commit()` * pointer-swaps them so the frame that just finished becomes * readable atomically and the building slot is free for the next * frame to overwrite. Pools grow to the frame's high-watermark and * never shrink. */ private _passPoolA; private _passPoolB; private _buildPasses; private _publishedPasses; /** Count of valid entries currently being built in `_buildPasses`. */ private _buildPassCount; /** Count of valid entries in `_publishedPasses` — what drain ships. */ private _publishedPassCount; private _batchPoolA; private _batchPoolB; private _buildBatches; private _publishedBatches; private _buildBatchCount; private _publishedBatchCount; /** Stack of in-flight pass indices for `beginPass`/`endPass` nesting. */ private _stackIndex; /** `renderer.info.render.calls` at each stack level's `beginPass`. */ private _stackEntryCalls; private _stackEntryTris; /** `performance.now()` at each stack level's `beginPass`. */ private _stackEntryTime; private _stackTop; /** * Monotonic counter — incremented once per `commit()`. Drain ships * whenever `_version !== _lastEmittedVersion`. No `_dirty` flag: the * version comparison IS the dirty check, and because commit is the * single place that advances it, there's no "was it published yet" * ambiguity. */ private _version; private _lastEmittedVersion; /** `true` when the feature has an active subscriber. Gate recording. */ private _capturing; /** Renderer counter baselines captured at `frameStart(renderer)`. */ private _frameBaseCalls; private _frameBaseTris; private _frameStartTime; /** Index of the implicit root "frame" pass in `_buildPasses`; -1 when not tracking. */ private _frameRootIdx; /** * Reset the BUILD counters for a new frame. Pointers + published * pool are untouched, so a flush landing before this frame's * `commit` still sees the previous frame's published snapshot. */ beginFrame(): void; /** Set by `DevtoolsProvider` based on subscriber state. */ setCapturing(on: boolean): void; isCapturing(): boolean; /** * Open the implicit "frame" root pass covering the whole frame. * Allocates the root event at index 0 of the BUILD pool and pushes * it onto the stack so subsequent `beginPass` calls nest under it. * Root counters are left at their previous values here; `frameEnd` * overwrites them before `commit` exposes the pool. */ frameStart(renderer: WebGPURenderer): void; /** * Close the root pass. Writes renderer-info deltas + wall-clock * duration into the build-pool's root event. Does NOT publish — * `commit()` does that at the end of `DevtoolsProvider.endFrame`. */ frameEnd(renderer: WebGPURenderer): void; /** * Record the start of a render pass. Snapshots renderer counters * and wall-clock start time so `endPass` can compute a delta. Labels * must be string constants — never concatenated per-frame. Nested * passes are tracked via an internal stack; `parent` wiring is * automatic. Writes go into the BUILD pool only. */ beginPass(label: string, renderer: WebGPURenderer): void; endPass(renderer: WebGPURenderer): void; /** * Walk every registered batch source and snapshot into the BUILD pool. * Two source families are merged into one output: * * - **ECS sources** return a `RegistryData` — tracked by * `SpriteGroup`'s Koota world. Reads `activeBatches` and pulls * material / layer / sprite-count out of trait data. * - **Mesh sources** return an iterable of `InstancedMesh` — * used by engine code that manages its own instanced meshes * outside the ECS (e.g. `TileLayer`'s per-chunk meshes). Each * mesh becomes one `BatchInfo` row. * * Both kinds land in the same batch pool so the inspector shows a * unified "Active batches" list regardless of which subsystem owns * the draw. Tile chunks sharing a material collapse into the same * run in the panel thanks to the runKey hash. */ captureAllSources(sources: ReadonlySet, meshSources: ReadonlySet): void; /** * Append engine-owned `InstancedMesh`es into the BUILD batch pool. * Each entry can be a raw `InstancedMesh` or a `{ mesh, kind, label }` * descriptor — the descriptor form lets sources carry subsystem * metadata (e.g. `kind: 'tilechunk'`, `label: 'chunk(0,2)'`) that * flows through to the inspector without a second data channel. * * `layer` comes from the mesh's `Object3D.layers.mask` (bit mask); * `materialId` prefers `Sprite2DMaterial.batchId` and falls back to * `Material.id`. Meshes sharing a material therefore collapse into * the same run in the panel, just like ECS batches do. */ captureMeshes(entries: Iterable): void; /** * Walk `registry.activeBatches` and append into the BUILD batch pool. * Callers typically invoke through `captureAllSources`; exposed * directly for tests. */ captureBatches(registry: RegistryData): void; /** * Atomically publish the build pool. Pointer-swaps build ↔ published * and bumps `_version`. Called by `DevtoolsProvider.endFrame` AFTER * `frameEnd` + `captureAllSources` have populated the build pool. * * Noop when not capturing so the version counter doesn't drift while * the feature is unsubscribed. */ commit(): void; /** * Ship the latest committed snapshot if it's newer than what we last * emitted. Returns `false` when the committed version hasn't * advanced since the last drain — in which case the `features.batches` * field is omitted from the wire payload and the client keeps its * previous snapshot (per protocol delta semantics). */ drain(out: BatchesPayload, frame: number): boolean; /** * Force the next drain to re-emit — rewinds `_lastEmittedVersion` so * the next `drain` call sees a "new" version. Called on every * `subscribe` so re-joining consumers get the current snapshot * (which may be empty if no frame has committed yet). */ resetDelta(): void; dispose(): void; } //#endregion export { BatchCollector }; //# sourceMappingURL=BatchCollector.d.ts.map