m02 · Systems · drill · 45 min
Remove every sync point from a system that must create and destroy
Why this is a drill and not a lab
The sync-point lab measured what a
That is the actual job. Nobody hands you a greenfield spawn system; they hand you one that works, stutters, and cannot be broken. The constraint that makes this hard is not performance — it is identical observable behaviour, which forbids the easy wins.
The starting point
Build or obtain a system with this shape — deliberately naive, and deliberately plausible:
- A spawn system that, each frame, decides how many entities to create and
creates them immediately via
EntityManager. - A despawn system that finds entities whose health has dropped to zero and
destroys them immediately, also via
EntityManager. - A damage system between the two that writes health, and occasionally adds
a
Dyingtag. - All three in
SimulationSystemGroup, in that order.
Get it running with a few thousand entities and enough churn that the stalls are visible in the Profiler. Measure the baseline before touching anything — the sync-point lab’s probe is the right instrument, and without that number your “improvement” is a feeling.
The conflicts, named
| Constraint | The shortcut it closes |
|---|---|
| Behaviour must be unchanged | Fixing the stutter by spawning less, which is not the exercise |
| No structural change in a job, no early complete | Completing a job to do the change “safely” on the main thread — the same barrier, hidden |
| No doing less work | Reducing churn instead of reducing barriers |
| Name the group for each barrier | Moving playback somewhere quiet and hoping |
| No mid-frame count polling | Reading EntityManager state to decide, which forces the sync you are removing |
The third and fifth are the ones people trip on. Reducing spawn rate makes the profile look better and teaches nothing. Polling entity counts to decide what to enqueue reintroduces the stall through a side door that does not look like a structural change at all.
How to work it
Milestones, not steps.
- Baseline, measured. How many barriers per frame, and what does the frame cost? Write both down. Everything is judged against these.
- Locate each one. For each barrier, name the exact call that causes it and the system it lives in. You should end up with a short list, and the list is the work plan.
- Defer, don’t delete. Each immediate structural change becomes an enqueued one. This step alone should not reduce the barrier count yet — you have moved the work, not consolidated it.
- Consolidate playback. This is the actual fix, and it is where the count drops. Deciding where playback belongs is the design decision the drill is testing.
- Verify behaviour, frame by frame. Log entity count every frame for 600 frames, before and after, and diff them. An off-by-one-frame difference is a real behavioural change and it will not show up in a final count.
- Re-measure. New barrier count, new frame time, and a sentence naming which stall you removed to get it.
When you are done
Answer without checking your code:
- How many barriers did you start with, how many remain, and what is each remaining one for?
- For the barrier you kept, what would break if you removed it? If the answer is “nothing,” why is it still there?
- Did any of your deferred changes land a frame later than before? Which, and did you decide that was acceptable or verify that it made no difference?
- If the despawn system needed to see entities the spawn system created in the same frame, what would that force, and does your current structure already handle it or would it need another barrier?
- What is the smallest change to the requirements that would make your solution wrong?
If you conclude that one of the barriers genuinely cannot be removed because a downstream system must observe the change immediately, that is a correct and valuable outcome — provided you can state precisely what observation forces it.