evy shardstore
five additions to ShardStore that let it serve as the ECS storage substrate for evy. all decisions below are made — no open questions remain.
context
evy routes all ECS component storage through bbg's ShardStore trait — transforms, animation phase, render buffers, and authenticated BBG polynomial state share one API. the four additions below are the only bbg changes required before evy_ecs_storage can be written. bbg has no knowledge of Bevy or entities; the trait stays at (dim, key, Goldilocks).
additions
1. ephemeral dimension
// storage/mod.rs — dim module
pub const EPHEMERAL: u8 = 12;
put(EPHEMERAL, ...) skips self.dirty.push. get(EPHEMERAL, ...) works normally. commit() ignores ephemeral entries. applies to all five backends: mem, unimem, fjall, redb, tiered. ~5 LOC per backend.
used for: Transform, AnimationPhase, VFXLifetime, RenderScratch — local-only state that must not gossip to the network or contribute to BBG_root.
2. mutating trait methods
add to ShardStore:
/// Returns mutable slice for in-place update.
/// Caller must call mark_dirty after writing (no-op for EPHEMERAL).
/// Returns None on backends that do not support in-place mutation (fjall, redb).
;
/// Marks an entry dirty for the next commit(). Required after get_mut writes.
/// No-op for EPHEMERAL dimension.
;
/// Removes an entry. Returns the previous value, or None if absent.
/// Must clear the dirty log entry if present.
;
MemStore: trivial HashMap ops.
UnimemStore: get_mut returns &mut [Goldilocks] over the pinned Block (mirrors existing put safety block); remove frees the Block and evicts from dirty.
fjall/redb: get_mut returns None; remove issues a delete.
remove is needed for Grid slot reuse with generation bumping. without it evy cannot reclaim unimem cells.
3. per-dimension iteration
add to ShardStore:
;
boxed to preserve trait object safety (TieredStore uses dyn ShardStore). concrete iterators require GATs and break object safety.
MemStore/UnimemStore: filter by dimension key prefix.
fjall/redb: prefix scan (dimension byte is the key prefix).
TieredStore: delegates to hot store; falls through to warm on miss.
4. UnimemStore slot pool
not a trait addition — UnimemStore-only:
put() checks if the dimension has a pool and routes to cell allocation instead of per-entry Block. UnimemStore tracks pool_dims: HashMap<u8, Pool> internally. ~80 LOC.
without this, per-entity Block allocation dominates render-frame cost on Apple Silicon.
concurrency
&mut self exclusive borrow is kept. evy batches writes per-thread and merges at frame boundary. this preserves BBG's clean ownership model and requires no interior mutability.
acceptance criteria
dim::EPHEMERAL = 12exists; all 5 backends skip ephemeral writes incommit()get_mut,mark_dirty,removeon trait; implemented onMemStoreandUnimemStore;get_mutreturnsNoneonfjall/redb; all documentediter(dim)on all 5 backends, returns boxed iteratorUnimemStore::reserve_pool+cellimplemented; pool cells have stable address untilremove()cargo test --workspace --all-featurespassesbbg/specs/storage.mdupdated: EPHEMERAL dimension, new trait methods,reserve_pool/cell, concurrency decision
scope
- session 1: EPHEMERAL across all backends + tests
- session 2:
get_mut/mark_dirty/remove+iteron all backends + tests - session 3:
reserve_pool/cellonUnimemStore+ benchmark vs per-entry Block + spec update