Ambiguous system ordering #1312
Replies: 7 comments 17 replies
-
Handling ambiguous system orderingBroadly speaking, there are three possible ways to handle ambiguous system ordering:
Option 1 is appealing, because it is trivial to implement, allows any behaviour the developer could want, and avoids creating a large number of warnings. However, it is also dangerous, because it does not provide any tools to detect these errors. Option 3 is strict, and guarantees correctness. However, it limits parallelism, and seriously impacts prototyping speed due to the requirement to manually specify dependencies (for the reasons discussed in the post above). Option 2 is the middle ground: detecting and warning about these ambiguities to allow but not require developers to fix it. Unless a further reason to forbid ambiguous system ordering completely arises (along with a better way to rapidly specify dependencies), this is the correct choice: all the power of option 1, but with some guide rails. |
Beta Was this translation helpful? Give feedback.
-
Specifying system dependenciesIn order to solve ambiguities, we need some way to specify system dependencies (within a stage). Of note: dependencies are directed, and systems can have more than one dependency, but the final graph of system dependencies must be acyclic in order to be resolved, creating a directed acyclic graph. There are three categories of system dependencies:
Soft dependencies are the default, as they avoid intermingling "should this system run" with "when does this system run", and prevents tangled non-local run criteria from making a mess of your systems in the way that hard dependencies do. Use soft dependencies for when running B before A is a logic error, use hard dependencies when B depends on A's results, and use explicit ambiguities when you don't care about the order in which A and B run, even though at least one of them has mutable access to data in common. |
Beta Was this translation helpful? Give feedback.
-
Reporting ambiguitiesA good warning system for ambiguities has the following technical properties:
It also has the following ergonomic properties:
Initial experiments@Ratysz has done very early experiments on reporting ambiguities. This approach reports ambiguities to the command line, separated by stage, and uses the type name to report conflicting systems when labels are unavailable. It provides some useful results, but fails the two technical criteria listed above and struggles badly with noisiness due to the lack of explicit ambiguities. Visually reporting ambiguitiesThis is universally acknowledged as "would be great" at some point in the future. We need a solid way to create data for it first though, and a better understanding of the patterns involved. Commutativity checkerBecause commutativity is such a powerful property for safely allowing non-obvious ambiguities, we could use property-based testing to check for commutativity automatically, notifying the user if two systems that are commutative are not allowed to run in parallel, or vice versa. This is very useful guidance, but will typically not be able to be done via static analysis at compile time (except perhaps in the simplest cases), so is likely suited to a manually triggered tool, to be run as part of integration testing. P.S. There's some nastiness around float math not being commutative: using a crate like float_eq and restricting to values seen in actual gameplay will help improve practical reliability. |
Beta Was this translation helpful? Give feedback.
-
More ergonomic dependency specificationPlaceholder post to summarize future suggestions. |
Beta Was this translation helpful? Give feedback.
-
A couple of corrections:
It does take archetypes into account, too. At least, it looked like it does - I did not write the archetype-component stuff, and it wouldn't have existed just because, so...
Not entirely true: not all systems that need to have a consistent execution order share common accessed data. Current executor assumes that they all do, it doesn't actually know. |
Beta Was this translation helpful? Give feedback.
-
Specifying Explicit AmbiguitiesThe ambiguity checker introduced in #1144 produces very noisy outputs. Many of these are false positives, and should be silenced. Explicit ambiguities between systems should be specified the same way #1144 lets you specify explicit dependencies: via system descriptors. An initial solution should be quick-to-implement and simple, in order to hit a release in 0.5. Proposal: Pairwise Ambiguities Sample syntax .add_system(system_1.system().ambiguous("system_2_label") This uses system labels, just like
Proposal: Ambiguity Sets via Set Labels IMO, explicit ordering ambiguities should be specified as a set for the following reasons:
During reporting, ignore any ambiguity between systems that are part of the same ambiguity set. During AppBuilder construction, error (or warn?) if systems belong to more than one label of this sort, since the sets should agglomerate into each other when there's any common members. Produce an error if two systems share a label but have an explicit dependency between them. Sample syntax for a "set label"-based approach: .add_system(my_system.system().ambiguous("ambiguity_set_label") Thoughts:
Proposal: Ambiguity Sets via Agglomeration Use the same underlying construct of ambiguity sets, but specify the relationship on a system-to-system basis. Agglomerate any sets produced in this way. During reporting, ignore any ambiguity between systems that are part of the same ambiguity set. Sample syntax .add_system(system_1.system().ambiguous("system_2_label") Thoughts:
|
Beta Was this translation helpful? Give feedback.
-
Archetype invariantsThe basic idea was proposed by @TheRawMeatball on Discord.
|
Beta Was this translation helpful? Give feedback.
-
Problem definition
In Bevy's initial scheduler, systems could automatically run in parallel, as long as they didn't access the same data. The previous scheduling rules ensure that, for each piece of data:
In order to break ties in the ordering of systems within the same stage, the old scheduler relies on the insertion order of systems into the
AppBuilder
.However, #1144 removes these strict guarantees, allowing (by default) any two systems to run in parallel as long as they are not attempting to access the same data (on a per archetype-component / resource basis) at the same time as a system has mutable access to it.
(For context, an archetype-component is a contiguous chunk of the data for a single component associated with a single archetype, the collection of entities that have the same components. This allows us to mutably access the same component in two different systems at once, as long as we know that there are no archetypes shared by those systems.)
This was done for the following very good reasons:
However, this resulted in the possibility of ambiguous system orderings, where a system that writes to a piece of data and a system that reads that same piece of data are not guaranteed to run in the same relative order every tick.
This can result in surprising, difficult to track logic bugs within the game, as changes may skip being processed (see #68) or important calculations may be done inconsistently. This problem is especially severe for some forms of networked code, where determinism of even inconsequential logic is important.
Note that, even with the previous scheduler, the absolute ordering of our systems was not guaranteed. This is generally fine, because we control data read / write access via
SystemParameters
. However, this is not always the case and can result in latency or unreliable behavior.Beta Was this translation helpful? Give feedback.
All reactions