-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
One Shot Systems #8963
One Shot Systems #8963
Conversation
Attempt 3 let's go :D Feel free to ping me with questions or complaints. |
I've fixed most of the mistakes made during rebasing. I do have a question about Other things I still need to do:
|
Renamed to |
Is
Is this still your opinion @alice-i-cecile? What cases could panic? I take the last part to mean that the system registry should look like this: #[derive(Resource, Default)]
pub struct SystemRegistry {
systems: Vec<(bool, SystemTypeSet<dyn Any>)>,
indices: TypeIdMap<usize>,
} |
Yeah, I think this got cut in the stageless refactor.
The concern is "if a user calls
I really dislike that design 😂 If at all possible I'd like to just do a |
I've added the following test for running unregistered systems with commands: #[test]
fn unregistered_system_command() {
fn command_system(mut commands: Commands) {
commands.run_system(spawn_entity);
}
let mut world = World::new();
assert_eq!(world.entities.len(), 0);
world.run_system(command_system);
assert_eq!(world.entities.len(), 1)
} And as expected this panics, but it doesn't panic where I expected it to.
It can't seem to find the system registry in the resources. I double-checked the error location with a debugger and found the following: (I added temp if that wasn't obvious) So while temp is not I am really confused. |
Oh: that's an artifact of how you're testing it. In order to call I ran into this previously, describing it as a difficulty in using one shot systems "recursively". As for a fix:
4 is my preferred solution if we can get it to work. 1 is a good "fix for now" if that's not easy. 2 would be okay but not great. 3 is simply bad. |
My initial intent was to fix unregistered systems panicking when being called through commands.
Instead, I stumbled into a separate issue, which I will add to the list:
While I think there's a way to implement nested one-shot systems cleanly, for now, I'll insert a better error message, as this PR is large enough already. |
I've completed one of the tasks, I have two questions:
|
I've attempted to reproduce the 'system not being registered through commands', but I have not been able to do so. The current test I still am really unsure what to do with the |
After some deliberation on Discord, it was decided that the use case for calling systems by their Id is very marginal and it was removed. It can be added later if a good use case is found. |
Example |
I've thought about this problem some more and due to the comments, I decided that for most substantial use cases it would be necessary to have a finer control over the system registry. So SystemIds are back and I'm slowly discovering what the purpose of this fence is. In my current implementation registering / run_by_id / removing would work as you expect. When
This, however, introduced two foot guns when it comes to using
These issues can be circumvented by using I'm not a huge fan of the current SystemId ergonomics as they basically are glorified pointers, and while pointers are not bad, they are very powerful and easy to abuse. |
I'm aware this isn't the most helpful type of review but IF you're frustrated with the current ergonomics my suggestion would be to rename |
I find |
I have a question regarding the CI. It says that there's an ambiguity when importing |
@Trashtalk217 It's in the main branch. bevy/crates/bevy_ecs/src/system/system.rs Line 241 in b88ff15
The CI will automatically rebase the PR based on main, just FYI. |
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 very happy with the state of this, and think it's important to move this forward to unblock UI uses of this.
That said, we need to think about the API in light of #9366.
The caching introduced in this PR is very useful for more complex uses of one-shot systems, where you care about performance, change detection and system-local state. So both are helpful!
That said, we need to clearly disambiguate the two APIs in the docs and method names.
Steps:
- Rebase this PR.
- Rename the methods for
run_system
from this PR torun_cached_system
or something to that effect. - Add notes to the docs of both methods to clarify the differences.
Further unification would be really nice, but I'm not immediately sure of the best way to do that.
it seems there is ambiguity with the name RunSystem. In #9366 , |
Additionally I think some sort of compatibility with #9366 would make this PR far more useful. Take for example a widget callback. Users will be interested in getting the components of the entity and to do that the callback system needs some way of knowing the entity it exists on. This could be done with #9366. cmds.spawn((
Rect,
Button,
Counter(0),
OnClick(reg.register(|In(entity): In<Entity>, mut counter: Query<&mut Counter>| {
counter.get_mut(entity).unwrap() += 1;
}))
)) fn handle_mouse_clicks(clickable: Query<(&Rect, &OnClick, Entity)>, registry: SystemRegistry, ..) {
if left_button_pressed {
if let Some((_, OnClick(sys), ent)) = clickable
.iter()
.find(|(rect, ..)| rect.contains(mouse_pos)) {
registry.run_by_id_with(ent, sys);
}
}
} |
I'm back. A lot of discussion has been happening. Let me just summarize a couple of my opinions. It is indeed quite silly to have a However, I am not quite ready for a full 'systems as entities', because that has a lot of knock-on effects and creates weird behavior (as @mxgrey pointed out). Granted it's not as bad as UB, but I'd still rather not have them. The trade-off between predictable behavior and unconstrained expressiveness is very real, but I want to stress that it only matters when users encounter problems with either side of this equation. The edge case that @mxgrey pointed out seems very unlikely, but also, as @iiYese pointed out, it's questionable whether or not some of the proposed features will ever see any use. I'd much rather act upon an issue by someone developing a game/library than act on speculation. Foresight is good, especially for library development, but there must be some limit. I've settled on a closed-off implementation, where the Briefly about the API. I'm thinking about removing the following endpoints.
My current working theory is that if I simply keep decreasing the size of this PR it may merge one day. Jokes aside, I'd like feedback on the API changes and the 'systems as almost entities' approach. It would be greatly appreciated. |
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 like this API quite a bit better: it's still nicely encapsulated but meaningfully simpler.
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.
This works for me. Just need to update the documentation.
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.
Looks good!
As a final note, run_system_once
probably doesn't need to be hidden behind a trait anymore (it can just be an inherent impl on world). We can save that for a follow-up though since it may be a more controversial change.
@james7132, @maniwani or @cart, can this PR be merged? |
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 can see the value in having this, especially the reactive use cases like UIs. The implementation seems good to me as well. I was worried that there would be safety concerns, but the current exclusive nature of these systems seems to avoid that. In turn, this does introduce a few performance concerns that users should be aware of.
…m` to get system output (#10380) # Objective Allows chained systems taking an `In<_>` input parameter to be run as one-shot systems. This API was mentioned in #8963. In addition, `run_system(_with_input)` returns the system output, for any `'static` output type. ## Solution A new function, `World::run_system_with_input` allows a `SystemId<I, O>` to be run by providing an `I` value as input and producing `O` as an output. `SystemId<I, O>` is now generic over the input type `I` and output type `O`, along with the related functions and types `RegisteredSystem`, `RemovedSystem`, `register_system`, `remove_system`, and `RegisteredSystemError`. These default to `()`, preserving the existing API, for all of the public types. --- ## Changelog - Added `World::run_system_with_input` function to allow one-shot systems that take `In<_>` input parameters - Changed `World::run_system` and `World::register_system` to support systems with return types beyond `()` - Added `Commands::run_system_with_input` command that schedules a one-shot system with an `In<_>` input parameter
I'm adopting this ~~child~~ PR. # Objective - Working with exclusive world access is not always easy: in many cases, a standard system or three is more ergonomic to write, and more modularly maintainable. - For small, one-off tasks (commonly handled with scripting), running an event-reader system incurs a small but flat overhead cost and muddies the schedule. - Certain forms of logic (e.g. turn-based games) want very fine-grained linear and/or branching control over logic. - SystemState is not automatically cached, and so performance can suffer and change detection breaks. - Fixes bevyengine#2192. - Partial workaround for bevyengine#279. ## Solution - Adds a SystemRegistry resource to the World, which stores initialized systems keyed by their SystemSet. - Allows users to call world.run_system(my_system) and commands.run_system(my_system), without re-initializing or losing state (essential for change detection). - Add a Callback type to enable convenient use of dynamic one shot systems and reduce the mental overhead of working with Box<dyn SystemSet>. - Allow users to run systems based on their SystemSet, enabling more complex user-made abstractions. ## Future work - Parameterized one-shot systems would improve reusability and bring them closer to events and commands. The API could be something like run_system_with_input(my_system, my_input) and use the In SystemParam. - We should evaluate the unification of commands and one-shot systems since they are two different ways to run logic on demand over a World. ### Prior attempts - bevyengine#2234 - bevyengine#2417 - bevyengine#4090 - bevyengine#7999 This PR continues the work done in bevyengine#7999. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Federico Rinaldi <[email protected]> Co-authored-by: MinerSebas <[email protected]> Co-authored-by: Aevyrie <[email protected]> Co-authored-by: Alejandro Pascual Pozo <[email protected]> Co-authored-by: Rob Parrett <[email protected]> Co-authored-by: François <[email protected]> Co-authored-by: Dmytro Banin <[email protected]> Co-authored-by: James Liu <[email protected]>
…m` to get system output (bevyengine#10380) # Objective Allows chained systems taking an `In<_>` input parameter to be run as one-shot systems. This API was mentioned in bevyengine#8963. In addition, `run_system(_with_input)` returns the system output, for any `'static` output type. ## Solution A new function, `World::run_system_with_input` allows a `SystemId<I, O>` to be run by providing an `I` value as input and producing `O` as an output. `SystemId<I, O>` is now generic over the input type `I` and output type `O`, along with the related functions and types `RegisteredSystem`, `RemovedSystem`, `register_system`, `remove_system`, and `RegisteredSystemError`. These default to `()`, preserving the existing API, for all of the public types. --- ## Changelog - Added `World::run_system_with_input` function to allow one-shot systems that take `In<_>` input parameters - Changed `World::run_system` and `World::register_system` to support systems with return types beyond `()` - Added `Commands::run_system_with_input` command that schedules a one-shot system with an `In<_>` input parameter
I'm adopting this
childPR.Objective
Solution
Future work
Prior attempts
&mut World
#2417This PR continues the work done in #7999.