-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Automatically add systems to sets based on their world accesses #8595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I really, really like these. As I've built out my game, and wrestled with questions of system ordering at scale, I've frequently found myself defining abstractions like this on my own. Furthermore, this information is invaluable for schedule visualization and inspection: the list of "all systems that write to X" is incredibly useful to plan out orderings and debug problems. Can we get some quick before / after benchmarking of schedule init time to make sure there's no huge regression? |
I ran our existing benchmarks related to the schedule, and there's just a couple of +-1% differences.
schedule/base time: [45.093 µs 45.184 µs 45.281 µs]
change: [-1.9576% -1.6382% -1.3208%] (p = 0.00 < 0.05)
Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
4 (4.00%) high mild
1 (1.00%) high severe
build_schedule/100_schedule_noconstraints However, note that our current benchmarks aren't a very good fit for measuring this since they use empty systems (no world accesses). |
I've thought of another unresolved question:
Adding an I think this question boils down to: Are access sets for systems that may access a component? Or is it for systems where the programmer intends to access a component? I'm leaning towards the second option personally. (Currently, this PR has an incorrect combination of both behaviors which I will need to fix.) |
I think the second one is much more useful. We could add the alternative mode as well under a different name if there's demand. |
.push(id); | ||
|
||
// If true, that means there are old systems that need to be initialized. | ||
let mut init_old = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(i'm new to bevy and i'm going through this PR to understand better the ECS)
Why can there be old systems that need to be initialized?
Is it be cause the AccessSet initialization runs before some system initialization?
Would it make sense to always make sure that AccessSet initialization runs at the very end of the initialization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to handle a situation like this
schedule.add_systems(my_system);
schedule.run(&mut world);
schedule.add_systems(other_system.after(Writes::<T>));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, The idea is sound, and the implementation seems to be as well.
(Future Note: Once Relations are obtained, this will need to be reimplemented using them)
I'd like extra attention to be paid to the implementation details used here, as I'm not 100% sure that the changes I made to |
@@ -378,6 +380,7 @@ impl ComponentDescriptor { | |||
storage_type, | |||
is_send_and_sync: true, | |||
type_id: None, | |||
is_resource: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike new
, there's no requirement that this method only be used for component types. I think we should pass in an argument to let users control that field directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I can add an argument for that. Seems like every one of my PRs is bound to have at least one breaking change at this point 😄
I have the same concerns with this PR. Whenever you use a Writes, and then add a new system that writes T. You now have to know where each Writes is used if you accidentally create an invalid schedule. This is especially a problem when you start including plugins, which should be considered as opaque containers. |
Closing this since I suspect the use cases will be very limited in real applications. If someone else wants to pursue this, feel free |
A third party version of this was recently published in https://crates.io/crates/bevy_sorting |
Objective
Resolves #7857.
Add a new type of
SystemSet
that is automatically populated with systems based on the world data they access. This is a powerful tool for ordering systems based on logical constraints rather than global reasoning. As an example of what this allows, you could schedule anEventReader
system to automatically run after any system that contains anEventWriter
. This has a few advantages:system_a.after(system_b)
, it isn't immediately clear why this dependency exists. If you writesystem_a.after(SendsEvent<T>)
, it is much more clear what purpose the dependency serves.EventWriter
system, you don't have to worry about forgetting to add a dependency to the relevant systems.Solution
Systems are automatically added to an "access set" if a system accesses the component associated with the set. Like system type sets, outside configuration is not allowed, and arbitrary systems may not be added to them. Access sets are created as-needed, meaning they will not be initialized or populated unless you actually use them.
Unresolved Questions
&World
/&mut World
count as reading/writing every single component? No, it should not.Changelog
Added the system sets
ComponentAccessSet
andResourceAccessSet
. Any systems that access a given type of component or resource are automatically added to the corresponding system set.