import type { ParticlesAsset } from '../core/model/Asset'; import { graphFingerprint } from '../core/graph/Graph'; import { compileParticles, type ParticlesBuild } from '../core/particles/compile'; import { gameClock } from '../core/time'; /** * Compiled particle graphs, one per asset, recompiled when the graph's content changes – the same * contract the `ShaderLibrary` keeps, including its own freeze so a test can hold particle time * still without freezing the shaders too. */ export class ParticleLibrary { private readonly builds = new Map(); /** How often a graph went through the compiler – what the checks read to prove a move did not. */ compiles = 0; private frozenAt: number | null = null; /** The clock every source's age is measured on: the global one, so a pause reaches the fields. */ now(): number { return this.frozenAt ?? gameClock.now; } freezeTime(at: number | null): void { this.frozenAt = at; } buildFor(asset: ParticlesAsset): ParticlesBuild { const fingerprint = graphFingerprint(asset.graph); const cached = this.builds.get(asset.id); if (cached && cached.fingerprint === fingerprint) return cached.build; this.compiles += 1; const build = compileParticles(asset.graph); this.builds.set(asset.id, { fingerprint, build }); return build; } } export const particleLibrary = new ParticleLibrary();