-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
run a system from a command #2234
Conversation
Why the extra refcell and mutex? |
This looks like it's one-shot, I think it's safe to take ownership when executing. What about |
pub struct RunSystem { | ||
system: Mutex<RefCell<Box<dyn System<In = (), Out = ()>>>>, | ||
} |
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.
So a few things with this.
-
I don't think we necessarily want to have this as
pub
since it's more of an implementation detail. -
as a small optimization, we could optionally declare the struct as such:
struct RunSystem {
system: UnsafeCell<Box<dyn System<In = (), Out = ()>>>,
}
unsafe impl Sync for RunSystem {}
IIUC, we can safely use UnsafeCell
and unsafe impl Sync
since we know no data races are possible as Commands
have exclusive mutable access to the World
.
Note: I'm not necessarily advocating for the unsafe
path, however, it is worth considering since then we don't have to worry about the overhead that Mutex
and RefCell
introduce.
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 is assuming you even need the UnsafeCell
in the first place...?
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 don't think we necessarily want to have this as pub since it's more of an implementation detail.
All commands are pub, so I would prefer to continue that way
as a small optimization, we could optionally declare the struct as such
I would prefer to avoid unsafe unless it's a clear gain in perf? But I don't think perf are a big issue for one off systems
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.
Cool, the pub
thing makes sense!
Though now that we removed the RefCell
, I'd still prefer the unsafe impl Sync for RunSystem
and remove the Mutex
, however I don't feel super strongly about this.
commands.spawn().insert(Player); | ||
commands.spawn().insert(Player); |
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.
Looking at this makes it seem like a copy paste error in my head haha 😅
Could we change this to be like for _ in 0..5
or something similar?
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 is a copy paste! from
bevy/examples/ecs/system_param.rs
Lines 33 to 35 in 653c103
commands.spawn().insert(Player); | |
commands.spawn().insert(Player); | |
commands.spawn().insert(Player); |
But I removed one just to be different
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 still prefer the for - in
syntax, but I guess I'm ok with how it is.
systems need to be mut to be initialised and executed, and we don't have a mut command. |
it works fine, added in the example 👍 |
Can you explain? Why wouldn't this work? impl Command for RunSystem {
fn write(mut self: Box<Self>, world: &mut World) {
self.system.initialize(world);
self.system.run((), world);
}
} |
Also, should we maybe just implement command for |
Why not both? |
Because that's not the signature of the trait function but it seems that's not a problem... so 👍 |
|
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.
That's an extremely simple implementation, wow.
I can expand more on how you might use this in the Bevy book (probably an advanced example) down the line.
@@ -178,6 +180,13 @@ impl<'a> Commands<'a> { | |||
}); | |||
} | |||
|
|||
/// Run a one-off [`System`]. | |||
pub fn run_system(&mut self, system: impl System<In = (), Out = ()>) { |
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 is a bit of bikeshedding, but I feel the name run_system
doesn't really express the intent of only running the system once. I could see someone easily getting confused thinking this adds the system to the overall app and expects is to execute each frame.
Perhaps we could use run_once
or one_shot
, run_one_shot
or something to that extent.
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.
one_shot
feels most natural to me, but I think it's not going to be clear to ESL users or those outside of gaming culture.
run_once
is probably best.
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 think it should still have system
in the name:
- makes it clearer and parallel with
add_system
- the command struct name should mirror the method on commands, and I think a
RunOnce
is very not clear from the name
run_system_once
? run_one_shot_system
?
What happens if the provided system calls commands itself? |
They (now) get executed directly after the system finishes (~90% sure?) |
90979aa
to
446dee4
Compare
Does this work with omitting |
of course it does 🥷 |
Is it ok to ask for an update on this? (no presh though, of course). |
So @wilk10 @zoeycode, IMO we're mostly waiting on consensus about "is this a feature we want to support, and for what use cases". This feature has some potential for footguns:
That said, this is trivial to implement as an end user who's familiar with the ECS internals: there's no special engine access needed. So perhaps we should take the time to do it right. What use cases would you like to use this for? That would be very useful input for us to both shape the feature and the supporting docs. |
My primary use case is UI code. Right now, one-offs are handled through events, but it requires explicit ordering to ensure changes go through on the same frame, but this gets more and more difficult the more levels you go through (for instance a popup which only appears under certain circumstances, otherwise it advances immediately). More generally, I think being able to run a system once would be useful as a means of replacing systems which listen every frame for events that only rarely occur. Also, I'm not sure how feasible this is, but it'd be nice to be able to pass arguments to one-off systems as well. |
Makes sense. bevyengine/rfcs#25 discusses a variant on this sort of pattern in the same UI context.
Yep, see #2192, which inspired this issue :)
You could maybe use system chaining for this. Local resources + .config might work better though? |
You can with a |
601aeb5
to
07da3b6
Compare
Co-authored-by: Nathan Ward <43621845+NathanSWard@users.noreply.github.com>
Co-authored-by: Alexander Sepity <ratysz@gmail.com>
@alice-i-cecile Thanks for looking into this! My main interest is to allow something like: use bevy::prelude::*;
use std::collections::VecDeque;
fn main() {
App::build()
.add_plugins(MinimalPlugins)
.add_startup_system(spawn.system())
.add_system(run.system())
.run();
}
#[derive(PartialEq, Eq)]
enum MyData {
A(u32),
B,
}
struct DataQueue{
queue: VecDeque<MyData>,
}
fn spawn(mut commands: Commands) {
let mut queue = VecDeque::new();
queue.extend(vec![MyData::A(42), MyData::B, MyData::A(17)]);
commands.insert_resource(DataQueue{queue});
}
fn run(mut commands: Commands, data: Res<DataQueue>) {
if let Some(my_data) = data.queue.front() {
match my_data {
MyData::A(_) => commands.run_system(system_a),
MyData::B => commands.run_system(system_b),
}
}
}
fn system_a(mut data: ResMut<DataQueue>) {
if let Some(my_data) = data.queue.pop_front() {
if let MyData::A(inner) = my_data {
println!("A: {}", inner);
}
}
}
fn system_b(mut data: ResMut<DataQueue>) {
if let Some(my_data) = data.queue.pop_front() {
if let MyData::B = my_data {
println!("B");
}
}
} AKA: arbitrarily running systems once in a dynamic order, accessing resources (or components) previously inserted. I have a bunch of Actions to perform broken down into a variable number of SubActions (eg: Action is "leave room", SubActions: "walk to door", "open door", "leave"), and this pattern is really convenient to easily compose sequences of SubActions each executed by a specific system that cannot be generalised. It's turn-based, so performance is less of an issue. |
07da3b6
to
a10fc4d
Compare
I'm closing this for now because I don't think "constructing systems on demand and running them serially" is a pattern we should encourage generally (see the comments in #2192). Happy to re-open if more arguments in favor of this direction are made, but I think we should be moving toward one (or both) of the following:
|
I added a tool to ergonomically run one-off systems in #3839 for the purpose of assertions. They're explicitly only useful for testing though, which means that the arguments around code reuse and performance are not relevant. |
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 #2192. - Partial workaround for #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 - #2234 - #2417 - #4090 - #7999 This PR continues the work done in #7999. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Federico Rinaldi <gisquerin@gmail.com> Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Alejandro Pascual Pozo <alejandro.pascual.pozo@gmail.com> Co-authored-by: Rob Parrett <robparrett@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: Dmytro Banin <banind@cs.washington.edu> Co-authored-by: James Liu <contact@jamessliu.com>
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 <alice.i.cecile@gmail.com> Co-authored-by: Federico Rinaldi <gisquerin@gmail.com> Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com> Co-authored-by: Aevyrie <aevyrie@gmail.com> Co-authored-by: Alejandro Pascual Pozo <alejandro.pascual.pozo@gmail.com> Co-authored-by: Rob Parrett <robparrett@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: Dmytro Banin <banind@cs.washington.edu> Co-authored-by: James Liu <contact@jamessliu.com>
Fixes #2192
Can run a system from a command.
This adds a command: