The frame and the component storage
The engine runs each fixed step as a schedule: an ordered list of systems, each declaring the components it reads and writes. Four
read commands show it, so an agent can answer “what runs, in what order, and could it run in parallel” and “is the component storage
laid out sensibly” without reading the code. The design is docs/design/ecs-v2.md.
| Question | 2D scene | 3D scene |
|---|---|---|
| What does a step consist of? | schedule.explain |
space3d.schedule.explain |
| How are the components stored? | ecs.stats |
space3d.ecs.stats |
schedule.explain
Section titled “schedule.explain”{"systems": [ {"name": "engine.physics", "stage": 100, "order": 0, "reads": [], "writes": ["resource:Physics"], "exclusive": true, "thread": "main"}, {"name": "engine.sync_transforms", "stage": 200, "...": "..."} ], "serialised": [ {"first": "engine.physics", "second": "engine.sync_transforms", "reason": {"kind": "exclusive", "keys": []}} ], "threads": 1}systemsis the canonical order: the order a one-thread run executes them (stage, thenorder, then name;after/beforeare hard constraints). It defines the result: a run with more threads gives the same state hash (docs/design/ecs-v2.md, section 5.4).reads/writesare component keys: registry names (Transform2D) for the engine’s own passes, Rust type names for systems a game adds in code.resource:*keys stand for state that is not a component (the physics world, the Lua VM, the contact list).exclusivesystems take the whole scene and always run alone, on the calling thread. The built-in passes are exclusive; systems a game adds withScene::add_system/Space3D::add_systemwork through queries and may run beside each other.serialisedlists every pair that can not overlap and why:exclusive,main_thread,write/writeorwrite/read(with the component keys), ordeclared order. A pair that is not listed may run at the same time on different threads.firstalways runs beforesecond.- Systems of loaded plugins come after the engine’s passes (
thread: main). They carry thereads/writestheir manifest declares (exclusive: truewhen it declares neither) and are held to them (docs/agent/plugins.md); for these pairsreason.kindcan also bemain_thread(two plugin systems whose declared access would allow an overlap: they all run on the main thread) orruntime_hook(a plugin system and a native query system of the scene whose access would allow an overlap: plugin systems run after the step). - A plugin system with
"parallel": trueis a main-thread query system: it is listedthread: main,exclusive: false, and pairs it has with native phase systems (PluginManager::with_phase_system) are not inserialisedwhen their access is disjoint, so they run at the same time with more than one thread (docs/agent/plugins.md). - A cycle in the declared order, or two systems with one name, is
E_SYSTEM_ORDER; a query system that touches a component it did not declare stops withE_SYSTEM_ACCESS(the message names the system and the component).
ecs.stats
Section titled “ecs.stats”{"entities": 100000, "change_tick": 4812, "heap_bytes": 9830400, "columns": [{"type": "sparklight_core::sprite::Sprite", "kind": "paged", "values": 100000, "pages": 391, "heap_bytes": 3010000, "occupancy": 0.997, "advice": null}]}kind:pagedkeeps the values in pages of 256 consecutive entity slots (fast; memory per touched page),sparsekeeps them packed and only the pages index them (memory per value). Both iterate in ascending entity order.occupancy(paged only) is values / (pages x 256).adviceis set when it is below 5%: the component is rare, andWorld::set_storage::<T>(StorageKind::Sparse)would cost memory per value instead of per page. The engine makes its rare components sparse (ScriptPath,Emitter,AnimatorState,TileLayer, …).change_tickis the world’s current change tick: the clock thatChanged<T>,Added<T>and the state-hash cache compare with.- Components defined by plugins are dynamic columns: rows named
dynamic:<Component>(kindsparseunless changed withWorld::set_dynamic_storage), same ordering, change ticks, snapshot and clone behaviour as the engine’s columns.