memory-pressure
PURPOSE
Detects memory pressure and GC pauses for the Canvas 2D game loop. Tracks Chrome JS heap usage when available and infers GC pauses from gaps between wall-clock frame time and measured JS execution time. Provides a per-run snapshot suitable for telemetry.
OWNS
- Module-local counters:
_gcPauseCount,_gcPauseTotalMs,_gcPauseWorstMs. - Capability flag
_hasMemoryAPI(computed once at module load from'memory' in performance). - Constant
GC_PAUSE_THRESHOLD_MS(5 ms) — the minimum gap between wall time and JS time before a suspected GC pause is recorded. - The
MemorySnapshotshape returned to callers.
READS FROM
performance.memory(Chrome-only, non-standard) — fieldsusedJSHeapSize,totalJSHeapSize,jsHeapSizeLimit. Guarded by_hasMemoryAPI.- Frame-time inputs supplied by the caller:
wallDtMs(rAF delta) andjsDtMs(sum of measured render-diag pass timings).
PUSHES TO
- Nothing. Pure module state; no events, no logging, no telemetry transport. Callers pull via
getMemorySnapshot()and forward as they see fit.
DOES NOT
- Does not allocate object pools, free buffers, or apply backpressure of any kind.
- Does not hint the GC, call
gc(), or invokeperformance.measureUserAgentSpecificMemory(). - Does not subscribe to or emit signals.
- Does not distinguish minor vs. major GC, nor separate GC pauses from compositor stalls — both manifest as wall-time-over-JS-time gaps.
- Does not work on Safari or Firefox for heap stats (those fields remain
null); GC pause inference still works on all browsers. - Does not auto-reset between runs — caller must invoke
resetMemoryPressure().
Signals
None. This module has no signal emissions or subscriptions.
Entry points
memoryTick(wallDtMs, jsDtMs)— call every frame. IfwallDtMs - jsDtMs > GC_PAUSE_THRESHOLD_MS, increments the suspected-GC counters by the full gap.getMemorySnapshot(): MemorySnapshot— returns heap stats (Chrome only;nullelsewhere) plus running GC pause counters. Heap values are rounded to 0.1 MB; ratio to 0.01.resetMemoryPressure()— zeroes all three GC pause counters. Intended to be called at run start.
Pattern notes
- GC pause detection is inferential, not authoritative. The browser does not expose a GC-occurred event, so this module uses the gap between rAF wall time and the caller’s measured JS time as a proxy. Any pause that happens between
performance.now()calls — GC, compositor stall, OS preemption — counts. - The 5 ms threshold is deliberately conservative. Minor GCs are typically 1–3 ms; setting the floor above that bracket avoids false positives from scheduling jitter at the cost of missing the lightest GCs.
- All state is module-local. There is no class, no singleton wrapper, no DI hook. Tests that need isolation should call
resetMemoryPressure()between cases. - The Chrome heap API is detected once at module load. If
performance.memoryis introduced after import (it isn’t, in practice), the module will not pick it up. - Heap values are pre-rounded inside
getMemorySnapshot(); consumers should not re-round. - Read-only consumption pattern: this module observes, it never acts. Decisions about pool sizing, allocation strategy, or asset eviction live elsewhere.