Skip to content

Instantly share code, notes, and snippets.

@Ctrlmonster
Created November 4, 2024 09:00
Show Gist options
  • Save Ctrlmonster/7f3d0732e88e07b2978ef05e6d7798c9 to your computer and use it in GitHub Desktop.
Save Ctrlmonster/7f3d0732e88e07b2978ef05e6d7798c9 to your computer and use it in GitHub Desktop.

Design Goals:

  • Minimizes risk of forgetting dependencies
  • Allows the user to make their mental model easy
  • Harmonizes with ecosystem

Intro

As a user I never have the full graph in my head. Usually I only picture "islands of order" in my head that relate to the current part/feature of the game I'm working on. With a gowing list of system the risk that I forget to add some explict system dependency grows linearly (maybe even more than that) and discovering a missing dependency might take very long or even get completely unnoticed as no critical bug appears right away, things are just out order for one frame and only bring a potential for failure to realize itself later on.

Therefore any abstractions should aid me in those two endeavour.

  1. allow me to author the graph in a way that reduces the risk of forgetting
  2. allow me to reason about larger pieces of the graph and their relation to each other

System Groups vs. System Tag

System Groups / Subschedules:

Easy mental model for creating "big chunks" of work, (i.e. gameplay, rendering, debug, deferred, etc.).

Example usecase: let's say I have a clean up system that by default needs to tick after all else. I don't want to add the dependecy everywhere, therefore, I create a group for all my "main" systems and schedule that group before clean up. Similar would go for "deferred systems" that perform work in iterator-like way, depending on how much time has already passed in the main schedule.

// create system group (returns a variable as shortcut, still part of the main schedule)
const gameplayGroup = schedule.addGroup("Gameplay");

// allow use of gameplaySchedule variable to reduce risk of forgetting "group: "Gameplay", see below

// equivalent:
gameplayGroup.add(UpdateInput);
schedule.add(UpdateInput, {group: "Gameplay"});

// equivalent:
gameplayGroup.add(UpdateCharacter, {after: UpdateInput});
schedule.add(UpdateCharacter, {group: "Gameplay", after: UpdateInput});
// ----------------------------------------------

// second group
const cleanupGroup = schedule.addGroup("Cleanup", {after: "Gameplay"});

// equivalent
schedule.add(DestroyEntities, {group: "Cleanup"});
cleanupGroup.add(DestroyEntities);

System Tags:

More like fixed "anchor points" to schedule relative to. Helpful when, e.g. I want to annotate systems as doing a certain thing / having a property, without grouping themselves together.

Example usecase:

Let's say I have a physics heavy game, with lots of explictly controlled bodies (elevators, moving platforms, rotating platforms, doors, jump pads, ...). All of these need to be scheduled before the physisc system gets updated. The risk is that I forget to add some of them to the physics dependencies as more types of controlled bodies get added. I could add all of them to a group, but the systems don't have interdependencies between them, it might make the design harder to change in the future (i.e. if specific systems in that group would need to be scheduled in a conflicting way, e.g. "openDoor" needs to be before Foo, but "moveLift" after Foo, that means I need to draw Foo into the group, even though it doesn't belong to that mental category, which the tag was representing).

// no inter-dependency between these two systems but they share a similarity 
schedule.add(UpdatePlatforms, {before: SyncTransformToMesh, tag: "ControlledBody"});
schedule.add(UpdateDoors, {before: SyncTransformToMesh, tag: "ControlledBody"});

// copying transforms needs to happen after all "ControlledBody" systems are done
schedule.add(CopyTransformToPhysicsBody, {after: "ControlledBody", before: UpdatePhysics});

Conclusion – when to use which:

System groups allow for an easy mental model, letting user reason about big blocks of work. Drawback is that whatever goes into one group can't have conflicting dependencies, without drawing ever more systems into the same group. System Tags allow the user to reason about all systems of a "specific type", without necessary viewing them as a single block. Conflicting dependencies can't happen in the same way as long as the tag itself can't be scheduled, but only scheduled relative to.

What's the difference, could this be one?

A crucial difference would be, groups can scheduled (i.e. groupA after groupB).
But tags can not be scheduled, only acts as schedule target (i.e. only systemA after tagA, but not tagA after systemB).

One could get both behaviors in one, if one allowed tags itself to be scheduled. Argument for this could a reduction in the number of concepts, argument against could be increased difficulty when mentally visualizing the order. My opinion is that it's harder to visualize tags as "anchor points", if the anchor point can itself be moving. Ideally it's something static in the background, that I can navigate/think relative to (north star on a boat kind of way). Also scheduling tags themselves brings back the "draw into group" issue described above.

Ecosystem Demands:

For good ecosystem interop, we need everything to be added to a single schedule in the end. This means that any group/sub-schedule abstraction needs to be completely "virtual", i.e. we'll find all systems within the same flattened graph.

We need to be able to integrate an externally authored module (i.e. collection of systems) while still being able to insert our own system in between. That means there can be no "black box" system group abstraction. You always need to be able to insert yourself between things. Conventions can help here too (i.e. lib authors exposing all the individual systems as schedule targets). If any conflicts arise (i.e. duplicate group names) it needs to be possible to manually override those (i.e. SomeGroup.name = "otherName").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment