Perf counters — always-on, near-zero-cost tallies for performance probes.
The render gate (scripts/render-gate/) compares a base and a candidate
build on counters such as bytes uploaded per frame, renders per
interaction, duplicate chunk decodes or console calls per step. Those
counters must exist on BOTH builds, cost nothing measurable on the hot
path, and be readable from bootstrap on (before the dataset loads), so
they live here as a module singleton rather than on a runtime component.
Usage on a hot path: resolve the slot ONCE at module scope, then pay one
typed-array store per event:
max keeps a high-water mark, gauge overwrites. record(kind, rec)
appends a structured record to a bounded per-kind ring (for per-tick
traces such as playback). Nothing here throws into the caller.
reset() starts a new measurement window: counters, high-water marks and
records go back to empty, but a GAUGE keeps its value. A gauge describes
current state (bytes pinned right now), and its owners republish it only
when it changes, so zeroing it would make the next reading lie until then.
A slot becomes a gauge the first time gauge() writes it.
Every counter is declared in PERF_COUNTERS (or matches a family in
PERF_COUNTER_FAMILIES), and the singleton only accepts those names,
so a misspelt counter is a compile error rather than a fresh zero the render
gate would read as n/a. The names are types only: a slot still resolves
once at module scope and every event is still one typed-array store.
Read via __luxarDebug.getPerf().counters (a flat name -> number map),
__luxarDebug.getPerfRecords(kind), and reset via
__luxarDebug.resetPerfCounters().
Perf counters — always-on, near-zero-cost tallies for performance probes.
The render gate (
scripts/render-gate/) compares a base and a candidate build on counters such as bytes uploaded per frame, renders per interaction, duplicate chunk decodes or console calls per step. Those counters must exist on BOTH builds, cost nothing measurable on the hot path, and be readable from bootstrap on (before the dataset loads), so they live here as a module singleton rather than on a runtime component.Usage on a hot path: resolve the slot ONCE at module scope, then pay one typed-array store per event:
maxkeeps a high-water mark,gaugeoverwrites.record(kind, rec)appends a structured record to a bounded per-kind ring (for per-tick traces such as playback). Nothing here throws into the caller.reset()starts a new measurement window: counters, high-water marks and records go back to empty, but a GAUGE keeps its value. A gauge describes current state (bytes pinned right now), and its owners republish it only when it changes, so zeroing it would make the next reading lie until then. A slot becomes a gauge the first timegauge()writes it.Every counter is declared in PERF_COUNTERS (or matches a family in PERF_COUNTER_FAMILIES), and the singleton only accepts those names, so a misspelt counter is a compile error rather than a fresh zero the render gate would read as
n/a. The names are types only: a slot still resolves once at module scope and every event is still one typed-array store.Read via
__luxarDebug.getPerf().counters(a flatname -> numbermap),__luxarDebug.getPerfRecords(kind), and reset via__luxarDebug.resetPerfCounters().