Skip to content

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

Closed
wants to merge 54 commits into from

Conversation

joseph-gio
Copy link
Member

@joseph-gio joseph-gio commented May 11, 2023

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 an EventReader system to automatically run after any system that contains an EventWriter. This has a few advantages:

  • Easier to write -- no need to manually add dependencies or assign systems to sets.
  • Captures programmer intent -- if you write system_a.after(system_b), it isn't immediately clear why this dependency exists. If you write system_a.after(SendsEvent<T>), it is much more clear what purpose the dependency serves.
  • Maintainability -- if you add a new 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.

schedule.add_systems((
    move_player_system,
    move_enemy_system,
    update_global_transform.after(ComponentAccessSet::Writes::<Transform>),
));

Unresolved Questions

  • Should there be a way of removing/hiding a system from an access set? How would this work and what API should we use?
  • Should &World/&mut World count as reading/writing every single component? No, it should not.

Changelog

Added the system sets ComponentAccessSet and ResourceAccessSet. Any systems that access a given type of component or resource are automatically added to the corresponding system set.

@alice-i-cecile alice-i-cecile added the X-Controversial There is active debate or serious implications around merging this PR label May 16, 2023
@alice-i-cecile
Copy link
Member

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?

@joseph-gio
Copy link
Member Author

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
time: [594.28 µs 596.49 µs 599.15 µs]
change: [+1.8818% +2.5395% +3.1193%] (p = 0.00 < 0.05)
Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
1 (1.00%) low mild
3 (3.00%) high mild
2 (2.00%) high severe
build_schedule/100_schedule
time: [14.612 ms 14.709 ms 14.822 ms]
change: [+1.0021% +1.7401% +2.5998%] (p = 0.00 < 0.05)
Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
2 (2.00%) high mild
4 (4.00%) high severe
build_schedule/500_schedule_noconstraints
time: [8.4230 ms 8.4956 ms 8.5714 ms]
change: [-1.5862% -0.3335% +0.9023%] (p = 0.61 > 0.05)
No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
Benchmarking build_schedule/500_schedule: Warming up for 500.00 ms
Warning: Unable to complete 100 samples in 15.0s. You may wish to increase target time to 58.4s, or reduce sample count to 20.
build_schedule/500_schedule
time: [573.97 ms 576.25 ms 578.70 ms]
change: [+0.2155% +0.7398% +1.2416%] (p = 0.01 < 0.05)
Change within noise threshold.
Found 6 outliers among 100 measurements (6.00%)
6 (6.00%) high mild
build_schedule/1000_schedule_noconstraints
time: [31.979 ms 32.344 ms 32.721 ms]
change: [-2.0313% -0.4846% +1.2116%] (p = 0.57 > 0.05)
No change in performance detected.
Benchmarking build_schedule/1000_schedule: Warming up for 500.00 ms
Warning: Unable to complete 100 samples in 15.0s. You may wish to increase target time to 355.9s, or reduce sample count to 10.
build_schedule/1000_schedule
time: [3.4761 s 3.4805 s 3.4851 s]
change: [+0.0899% +0.2712% +0.4590%] (p = 0.00 < 0.05)
Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
4 (4.00%) high mild

However, note that our current benchmarks aren't a very good fit for measuring this since they use empty systems (no world accesses).

@joseph-gio
Copy link
Member Author

I've thought of another unresolved question:

Should &World/&mut World count as reading/writing every single component?

Adding an &World system to the read-access set for every single component seems like the most obviously correct approach, however I feel it would make this feature less useful since potentially many unrelated systems will be grouped into an access set.

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.)

@alice-i-cecile
Copy link
Member

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.

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;
Copy link
Contributor

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?

Copy link
Member Author

@joseph-gio joseph-gio Sep 10, 2023

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>));

@alice-i-cecile alice-i-cecile requested a review from hymm September 22, 2023 09:58
Copy link

@Diddykonga Diddykonga left a 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)

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Oct 9, 2023
@alice-i-cecile alice-i-cecile added this to the 0.13 milestone Oct 9, 2023
@joseph-gio
Copy link
Member Author

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 ComponentDescriptor are correct.

@@ -378,6 +380,7 @@ impl ComponentDescriptor {
storage_type,
is_send_and_sync: true,
type_id: None,
is_resource: false,
Copy link
Member

@alice-i-cecile alice-i-cecile Oct 10, 2023

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.

Copy link
Member Author

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 😄

@hymm
Copy link
Contributor

hymm commented Oct 21, 2023

from cart on discord — Yesterday at 4:11 PM
I'm a bit worried about this pattern. Seems like it would be very easy to create invalid schedules. Ex: Something that needs to run after Reads but before a specific system that also happens to read T. It also seems lossy because exclusive systems wouldn't report what they read to the scheduler

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.

@joseph-gio
Copy link
Member Author

joseph-gio commented Mar 24, 2024

Closing this since I suspect the use cases will be very limited in real applications. If someone else wants to pursue this, feel free

@joseph-gio joseph-gio closed this Mar 24, 2024
@alice-i-cecile
Copy link
Member

A third party version of this was recently published in https://crates.io/crates/bevy_sorting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Controversial There is active debate or serious implications around merging this PR
Projects
Status: Open PR
Development

Successfully merging this pull request may close these issues.

Add systems to sets based on their world accesses
7 participants