-
-
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
One-shot systems through Commands and Schedules #7707
Comments
Saying "I want a way to add commands to the command queue within commands" would help understand better what you are requesting 😉 Hmm, maybe I'm mistaken. You might be asking for a way to use commands to manipulate the schedule graph, adding and removing systems. I would advise against it, or designing your game around a similar concept. Here are the cons:
You can already use struct SystemCommandCache<T: SystemParam>(T::State);
trait SystemCommand {
type Param: SystemParam;
fn sys_write(params: SystemParamItem<Self::Param>);
}
impl<T: SystemCommand> Command for T {
fn write(self, world: &mut World) {
if !world.contains_resource::<SystemCommandCache<T::Param>>() {
// default system state value
}
world.scope(|mut cache: Mut<SystemCommandCache<T::Param>>, world| {
let mut system_state: SystemState<T::Param> = SystemState::new(world);
let mut command_write_params = system_state.get_mut(world);
self.sys_write(command_write_params);
// TODO: caching as described in the SystemState docs
});
}
} This definitively doesn't compile, but I'm fairly certain with some work you can get something like that compiling. Now there is still the issue of applying commands afterward. I'm not sure when commands added through your system here will be ran. And if there is a way to immediately trigger a command flush at the exit of a command flush. I doubt it's a good idea to manipulate the command queue within a command, Trivially, one can deduce it is going to be very challenging to implement, especially with regard to exclusive mutable access. Looping the command application seems a bit more complex than it is worth. I've a few prototype with an unreasonable amount of trait shenanigans, one involves literally storing systems in |
I've updated the original post, thank you. Your criticism is fair, let me address it to better explain my use case.
I know Bevy offers great tools to create game simulations. Working with schedules, system ordering and timesteps is great and the most ergonomic implementation I've found for simulations of this kind. But I'm building an editor and not a game. This should be a fully supported use case because:
I'd have to think more about it, but the command pattern might also be a great way to implement turn based games.
Some use cases are inherently very difficult to parallelize and are willing to take the performance hit for other kind of guarantees. I want to skip the schedules completely and just apply changes to the world in a sequential manner, without having a gigantic system that needs to query all kinds of stuff. Commands are the perfect fit, but currently less ergonomic than systems. So this proposal is just trying to keep the exact same functionality that commands have right now, but making them more ergonomic. As you can see in the "alternatives" section, I'm not the only one who wants this (or something very similar) and many others have proposed solutions to this problem.
I know the
I was pleasantly surprised to find out that the command stack works exactly as I would expect it to work in this regard. You can see it in my example:
There is no need for extra flushes. If any of these commands inserted a
I'll keep an eye out, trait shenanigans can be very tricky :) |
Inspired by the @nicopap comment and @alice-i-cecile work in #4090 I've been able to improve the API. But this is still a hacky implementation because I don't fully understand Bevy internals. The new code supporting the implementation no longer uses schedules (which renders my title outdated 😩). // I need to use the new type pattern to add the new functionality
struct SystemCommand<T: 'static + Send + Sync>(T);
impl<T: 'static + Send + Sync> Command for SystemCommand<T> {
fn write(self, world: &mut World) {
world.resource_scope(|world, mut cache: Mut<SystemCommandCache<T>>| {
cache.0.run(self.0, world);
cache.0.apply_buffers(world);
});
}
}
// this is the main reason to call this solution hacky
// SystemTypeIdLabel and a centralized system registry are likely a better solution
// (see #4090 for more details)
#[derive(Resource)]
struct SystemCommandCache<T: 'static + Send + Sync>(Box<dyn System<In = T, Out = ()>>);
// implemented as a trait only for convenience
pub trait RegisterSystemCommand {
fn register_system_command<T: 'static + Send + Sync, P>(
&mut self,
system: impl IntoSystem<T, (), P>,
) -> &mut Self;
}
impl RegisterSystemCommand for App {
fn register_system_command<T: 'static + Send + Sync, P>(
&mut self,
system: impl IntoSystem<T, (), P>,
) -> &mut Self {
let mut system = IntoSystem::into_system(system);
system.initialize(&mut self.world);
self.insert_resource(SystemCommandCache(Box::new(system)))
}
}
// implemented as a trait only for convenience
pub trait AddSystemCommand {
fn add_system_command<T: 'static + Send + Sync>(&mut self, command: T);
}
impl<'w, 's> AddSystemCommand for Commands<'w, 's> {
fn add_system_command<T: 'static + Send + Sync>(&mut self, command: T) {
self.add(SystemCommand(command));
}
} And the same example as before with the new improved API. fn main() {
App::new()
.init_resource::<Numbers>()
.register_system_command(count_to)
.register_system_command(write_number)
.add_startup_systems((add_commands, apply_system_buffers, read_numbers).chain())
.run();
}
#[derive(Resource, Default, Deref, DerefMut)]
struct Numbers(Vec<i32>);
fn add_commands(mut commands: Commands) {
commands.add_system_command(CountTo(3));
commands.add_system_command(WriteNumber(100));
commands.add_system_command(CountTo(2));
}
struct CountTo(i32);
fn count_to(In(CountTo(number)): In<CountTo>, mut commands: Commands) {
for i in 1..=number {
commands.add_system_command(WriteNumber(i));
}
}
struct WriteNumber(i32);
fn write_number(In(WriteNumber(number)): In<WriteNumber>, mut numbers: ResMut<Numbers>) {
numbers.push(number);
}
fn read_numbers(numbers: Res<Numbers>) {
for number in &**numbers {
println!("{number}");
}
} |
I'm going to close this for now in favor of #7999. See |
What problem does this solve or what need does it fill?
I've found other attempts at solving this problem, it's well explained in #4090. Basically I want to be able to use the command pattern with
SystemParam
s.Commands should also keep their ability to trigger behavior associated with concrete data.
And IMO the command pattern should also support adding commands to the command queue within commands. This is one of their main advantages compared to events. I believe this is currently completely supported so we just need to keep the behavior as is.
What solution would you like?
In an ideal world you would be able to simply add your
SystemParam
s to thewrite
function.This is obviously not possible because it doesn't comply with the
write
signature defined in theCommand
trait. I'm not sure if it would be possible to do it with some tricks and magic and then register the command so that itsSystemState
gets cached.Anyhow, the next best solution I can think off is to allow systems to take a command as an input.
The closest thing I've been able to do to implement this solution is through schedules and resources.
First, the code that supports this hacky solution.
And a simple example using this solution.
The output of the program is consistent with the behavior commands should have when inserting other commands.
As you can probably tell, I'm not super familiar with the Bevy internals and my solution is only interacting with its surface. I would love to read your suggestions on how to improve this and your overall feedback on this type of solution.
The most obvious concerns right now are getting the "ideal" syntax working and analyzing the performance impact of running all these different schedules. To avoid regressions we could have
ExclusiveCommand
s to reproduce the current behavior of taking the whole world with no need for an schedule.What alternative(s) have you considered?
There are many other alternatives, but none of them fit these particular requirements.
Command
s -- allow commands to useSystemParam
s #7252I apologize if I've misrepresented any of these solutions out of ignorance.
Additional context
If you want to experiment with this I've set up a small repo you can fork:
https://github.com/pascualex/bevy_system_commands
The text was updated successfully, but these errors were encountered: