Step 1 of 5 · Reading · ~3 min
Read
Entity-Component System
Systems & Schedules
A system is a function that runs once per frame over every entity that has some set of components. An engine has dozens of them, and the order they run in decides what the player sees. After this lesson your program will take a list of systems, each declaring what it reads and writes, and produce a schedule that runs the safe ones side by side and keeps the rest in order.
Why order matters
Systems communicate through data, so each one sees whatever the previous ones left behind:
- Movement before rendering: the picture shows this frame's positions.
- AI before movement: AI decides a heading, movement applies it. Reversed, everything reacts one frame late.
- Input before AI: the enemies see where the player is now, not where they were.
Run in the wrong order and nothing crashes. The game just feels laggy or inconsistent, which is the worst kind of bug to find.
Declaring access
Each system states the data it reads and the data it writes. "Data" means component types, and also resources: singletons such as Time or Input that belong to the world rather than to one entity. Declarations let the engine reason about systems without running them.
Two systems conflict when one of them writes something the other reads or writes:
reads A, reads A no conflict (both only look)
reads A, writes A conflict (reader may see a half-updated world)
writes A, writes A conflict (last writer wins, order decides the result)
writes A, writes B no conflict (disjoint data)
Conflicting systems must run one after the other. Non-conflicting systems can run at the same time on different threads. That is how Bevy and similar engines use all the cores without locks.
Two more tools:
- Explicit ordering. A system may say it must run after another even when their data is disjoint, for example "show the score after the game rules ran".
- Exclusive systems. One that needs the whole world (applying queued spawns and despawns, saving a game) is declared as writing everything; it conflicts with every other system and runs alone.
From conflicts to stages
Keep the order systems were registered in; it breaks every tie and makes frames reproducible. Then place each system, in that order, into the earliest stage that comes after every earlier system it conflicts with or must follow. Systems in the same stage can run in parallel; stages run one after another.
Worked example with different material:
SYSTEM spawner - Enemies -
SYSTEM health Armor Hp -
SYSTEM poison Hp Hp -
SYSTEM minimap Enemies - -
SYSTEM hud Hp,Armor - spawner
spawnerconflicts with nothing: stage 0.healthwrites Hp, nothing earlier touches Hp: stage 0.poisonreads and writes Hp, conflicts withhealth: stage 1.minimapreads Enemies, conflicts withspawner: stage 1.hudreads Hp, so it followshealthandpoison(stage 2); it also namesspawner, which is only stage 0: stage 2.
stage 0: spawner health
stage 1: poison minimap
stage 2: hud
Common mistakes
- Treating two readers as a conflict. Read-only access is the whole reason parallelism is possible.
- Treating a reader registered before a writer as harmless. The writer still must not run beside it.
- Taking only the previous system into account. A system must wait for the latest conflicting stage among all earlier ones.
- Forgetting that an exclusive system conflicts even with systems that declare no access at all.
- Moving a system to an earlier stage than a system it follows because their data is disjoint.
Your exercise
Build the scheduler described in the exercise. The starter parses the declarations and prints the stages; you write the function that assigns each system its stage. The most likely mistake is looking only at the previous system.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…