-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
What problem does this solve or what need does it fill?
Ensuring that one system A has run before the next system B is an extremely common pattern, allowing us to ensure that the data in B has been pre-processed appropriately before we handle it.
As discussed in the alternatives section, there are three ways to get similar behavior: implicit declaration order, stages and system chaining. All of these are inadequate for the common case where you simply care that A has completed before B begins.
Describe the solution would you like?
Option 1. Remove the implicit guarantee that systems added first within a stage run before those added after it. This encourages people writing confusing, brittle code that can't be refactored safely. In its place, add a data-structure that unambiguously specifies the dependency graph of conflicting access to data within a stage, ideally verified at compile-time.
The total order created by the system registration order is sufficient, but not necessary, to ensure that we can always find a deterministic system ordering. While this total ordering is intuitive for small programs, it becomes unwieldy for games with more than one file adding systems to the same stage and the overspecification can cause unpredictable changes to unrelated systems.
Instead, we can get away with constructing a directed acyclic graph, with all systems within the stage as a node. In order for a system to be scheduled, each of its parents must be complete. But much of this graph can be constructed automatically: only key relationships between systems that share conflicting component / resource access need to be disambiguated.
This ensures that ordering constraints of each stage is well-documented in a single place, trivial to helpfully visualize, easy to consume for new schedulers and must be manually changed when important new behavior is modified.
If we ensure that systems are static (and instead toggle optionality with run criteria and custom schedulers), this graph can be constructed and verified unambiguous at compile time, making it a powerful dev tool to prevent painful ordering-driven logic bugs and identify system parallelism bottlenecks.
Option 2. Add a good way to examine and modify the system registration order, in order to detect and fix problems created by system ordering. Adding systems before or after another system could then, at game startup, simple squish into the registration order in a postprocessing step, bumping later entries down. Of course then, if you have multiple systems being added after the same system that was inserted in the ordinary way, you've reproduced the pattern and probably need to rely on registration order of the ordered systems as well :(
Describe the alternative(s) you've considered?
-
Implicit declaration order: simply declare the systems in order, and the scheduler makes sure they obey certain rules. This is very hard to follow, prone to being accidentally broken in mysterious ways, and forces us to record and verify the declaration order in our scheduler. The problems with this get much worse as the project grows, and systems are added in plug-ins, with the timing interacting in mysterious ways based on the arbitrary order that the plug-ins themselves are added.
-
Stages: You can ensure that one system runs after another by ensuring that they're in distinct stages. But this introduces a serious parallelism hit: rather than only waiting for your dependencies to complete, you're also stuck waiting for all the other systems in that stage to complete. On larger projects, I could also see this producing a messy proliferation of stages whose purpose and relation becomes unclear.
-
System chaining: This pattern seems much better suited to simple utility functions added on to systems. You do not seem to be able to delay execution, or ergonomically create branching dependencies in either direction.
Additional context
This is closely related to the discussion in #1021, Stages v2. Having an explicit way to ensure system ordering would make the process of entering and exiting states much cleaner, and this feature will need integration with the .with_run_criteria discussed there.