Coroutine typedef
A resumable piece of gameplay logic, written as a sync* generator.
Iterable entrance(Entity self) sync* {
yield 0.5; // wait half a simulated second
opacity[self] = 1.0;
yield null; // wait one fixed step
yield WaitUntil(() => landed);
playSound(self);
}
Why sync* and not async*
This looked like Stream<FutureOr<double?>> Function() first, which is the
obvious Dart spelling and is wrong here for a reason that has nothing to
do with style. An async* generator resumes on a microtask, and this
engine requires every component write to land between MemoryPool.beginTick
and commitTick - data_layout.dart asserts it, because beginTick copies
the last published snapshot over the write slot and would silently discard
anything written outside that window.
A coroutine exists to write component data after waiting. Under async*
every one of those writes lands on a microtask after commitTick and is
therefore thrown away: silently in release, on an assert in debug. Not
intermittently - every time, for every write after the first yield.
sync* resumes synchronously, so CoroutineScheduler.step can drive it
from inside the tick window and the writes land where they must. It is also
what Unity's IEnumerator has always been, for what is probably the same
reason.
What a yield may be
null- resume on the next fixed step.- a
num- resume after that many simulated seconds, accumulated fromGame.fixedTimeStep. Simulated rather than wall-clock, so a coroutine replays identically;Future.delayedwould not. - a YieldInstruction - resume when it says so, polled once per step.
- another
Iterable- run it to completion first, then carry on. Nesting is a plain stack, so a coroutine can be composed of coroutines without either knowing about the other.
Anything else is a programming error and throws, rather than being silently treated as "next frame".
The element type is left off deliberately. A coroutine yields a mixed bag by
design - a null, a number, an instruction, another coroutine - so there is
no element type worth writing, and Iterable is both shorter and more
honest than pinning it to Object?.
Implementation
typedef Coroutine = Iterable Function();