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 MemorySnapshot shape returned to callers.

READS FROM

  • performance.memory (Chrome-only, non-standard) — fields usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit. Guarded by _hasMemoryAPI.
  • Frame-time inputs supplied by the caller: wallDtMs (rAF delta) and jsDtMs (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 invoke performance.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. If wallDtMs - jsDtMs > GC_PAUSE_THRESHOLD_MS, increments the suspected-GC counters by the full gap.
  • getMemorySnapshot(): MemorySnapshot — returns heap stats (Chrome only; null elsewhere) 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.memory is 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.