Templates::EnvCache
Pool of Crinja environments.
Croupier spawns fresh worker fibers for every wave (and for every rebuild in auto mode), so a per-fiber cache would rebuild each environment - and re-parse every template in it - on each wave, while leaking the dead fibers' entries.
Environments are checked out lazily on first use by the fiber running a task (so tasks that never render, like image processing, don't create one) and returned by FeatureTask's ensure block, so environments and their template caches are reused across waves without ever being shared by two concurrent workers.
The pool used to be a mutex-guarded Hash + Array; under a parallel build every task checks an environment out (and back in) several times, and blocking acquisitions serialize all workers on scheduler park/unpark cycles (~70us each), which dominated profiled build time. Instead, checkout state lives in fixed-size parallel arrays guarded by an atomic free-slot bitmask:
- @@envs holds the pooled environments themselves (a plain, GC-scanned array: references must never be stored inside atomics, or the collector cannot see them).
- @@owners[i] names the fiber currently holding slot i (written only by the winning fiber itself, so plain reads are safe).
- @@free_mask has bit i set while slot i is unclaimed; claiming is a CAS loop on it.
Nothing here ever blocks: the fast path is an O(slots) scan of @@owners, the slow path is a bitmask CAS. When every slot is taken (more concurrent rendering fibers than slots) a transient unpooled environment is created instead; its template cache dies with the task, which only costs a re-parse of the few templates it uses.
Class methods
Fast path: does any slot already belong to the current fiber?
Return the current fiber's environment to the pool, if it has one. Transient environments (all slots were busy) are dropped.