flutter3d
Showcase Changelog 38 packages API reference

Systems and events

since 0.5.0 Simulation, audio and XR

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',
  );
}