Systems and events
Open the live demo · Read the source · View on GitHub
A genre package owns its own step, but a game built on top of it often wants
to hang extra work off a point in that step without forking it. StepSystems
is that seam: a game registers a function against a phase, and the genre
announces the phase without needing to know what the game hung there.
Step 1: Register systems against a phase #
Two systems are added to the same phase here, one that only logs and one that
raises the score. order decides which runs first; registration order only
breaks a tie.
final systems = StepSystems();
final events = GameEvents();
var score = 0;
final order = <String>[];
final logging = systems.add(
StepPhase.begin,
(StepContext step) => order.add('logging'),
order: 10,
label: 'logging',
);
systems.add(
StepPhase.begin,
(StepContext step) {
order.add('scoring');
score += 5;
events.add(_ScoreChanged(score));
},
order: 0,
label: 'scoring',
);
Step 2: Run the phase and drain its events #
run calls every system registered for a phase, in order. GameEvents is a
buffer a system writes into and a frame reads afterwards, in the same step,
never across a gap where something else could have changed the world in
between.
// Registered "logging" first but ordered after "scoring": order wins,
// and only ties fall back on registration.
systems.run(StepPhase.begin, 1 / 60);
final drained = events.drain();
final firstOrder = List<String>.of(order);
The scoring system has the lower order, so it runs before the logging
system even though logging was registered first. The event it raised is still
sitting in the buffer until something drains it.
Step 3: Remove a system #
add returns a handle, and that handle is what takes the system back out
again. A system removed this way does not run on the next step.
// The handle `add` returned removes the system again; a step run after
// that no longer calls it.
systems.remove(logging);
order.clear();
systems.run(StepPhase.begin, 1 / 60);
final afterRemoval = List<String>.of(order);
Step 4: Watch the order run #
Three systems, registered in one order and given another: input at −10,
scoring at whatever the slider says, logging at 10. Every 0.6 s a step
runs the phase, and each of the three slots lights in the colour of whichever
system ran in that place: blue for input, yellow for scoring, purple for
logging. Slide the scoring order past 10 and it moves behind logging;
un-register logging and its slot goes dark. The yellow tower is the score that
scoring keeps adding to.
final StepSystems systems = StepSystems();
systems.add(
StepPhase.begin,
(StepContext step) {
_ran.add('scoring');
_score += 5;
},
order: scoringOrder.round(),
label: 'scoring',
);
systems.add(
StepPhase.begin,
(StepContext step) => _ran.add('input'),
order: -10,
label: 'input',
);
if (loggingOn) {
systems.add(
StepPhase.begin,
(StepContext step) => _ran.add('logging'),
order: 10,
label: 'logging',
);
}