Skip to content

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
{"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}
  • systems is the canonical order: the order a one-thread run executes them (stage, then order, then name; after/before are 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 / writes are 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).
  • exclusive systems take the whole scene and always run alone, on the calling thread. The built-in passes are exclusive; systems a game adds with Scene::add_system / Space3D::add_system work through queries and may run beside each other.
  • serialised lists every pair that can not overlap and why: exclusive, main_thread, write/write or write/read (with the component keys), or declared order. A pair that is not listed may run at the same time on different threads. first always runs before second.
  • Systems of loaded plugins come after the engine’s passes (thread: main). They carry the reads / writes their manifest declares (exclusive: true when it declares neither) and are held to them (docs/agent/plugins.md); for these pairs reason.kind can also be main_thread (two plugin systems whose declared access would allow an overlap: they all run on the main thread) or runtime_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": true is a main-thread query system: it is listed thread: main, exclusive: false, and pairs it has with native phase systems (PluginManager::with_phase_system) are not in serialised when 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 with E_SYSTEM_ACCESS (the message names the system and the component).
{"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: paged keeps the values in pages of 256 consecutive entity slots (fast; memory per touched page), sparse keeps 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). advice is set when it is below 5%: the component is rare, and World::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_tick is the world’s current change tick: the clock that Changed<T>, Added<T> and the state-hash cache compare with.
  • Components defined by plugins are dynamic columns: rows named dynamic:<Component> (kind sparse unless changed with World::set_dynamic_storage), same ordering, change ticks, snapshot and clone behaviour as the engine’s columns.