Support fallible one-shot systems (#19678)

Closes #19677.

I don't think that the output type needs to be `Send`. I've done some
test at it seems to work fine without it, which in IMO makes sense, but
please correct me if that is not the case.
This commit is contained in:
Alejandro Pascual 2025-06-17 21:48:37 +02:00 committed by GitHub
parent 2915a3b903
commit d1c6fbea57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 15 deletions

View File

@ -144,10 +144,11 @@ where
/// A [`Command`] that runs the given system, /// A [`Command`] that runs the given system,
/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource.
pub fn run_system_cached<M, S>(system: S) -> impl Command<Result> pub fn run_system_cached<O, M, S>(system: S) -> impl Command<Result>
where where
O: 'static,
M: 'static, M: 'static,
S: IntoSystem<(), (), M> + Send + 'static, S: IntoSystem<(), O, M> + Send + 'static,
{ {
move |world: &mut World| -> Result { move |world: &mut World| -> Result {
world.run_system_cached(system)?; world.run_system_cached(system)?;
@ -157,11 +158,15 @@ where
/// A [`Command`] that runs the given system with the given input value, /// A [`Command`] that runs the given system with the given input value,
/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource. /// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource.
pub fn run_system_cached_with<I, M, S>(system: S, input: I::Inner<'static>) -> impl Command<Result> pub fn run_system_cached_with<I, O, M, S>(
system: S,
input: I::Inner<'static>,
) -> impl Command<Result>
where where
I: SystemInput<Inner<'static>: Send> + Send + 'static, I: SystemInput<Inner<'static>: Send> + Send + 'static,
O: 'static,
M: 'static, M: 'static,
S: IntoSystem<I, (), M> + Send + 'static, S: IntoSystem<I, O, M> + Send + 'static,
{ {
move |world: &mut World| -> Result { move |world: &mut World| -> Result {
world.run_system_cached_with(system, input)?; world.run_system_cached_with(system, input)?;
@ -175,7 +180,7 @@ where
pub fn unregister_system<I, O>(system_id: SystemId<I, O>) -> impl Command<Result> pub fn unregister_system<I, O>(system_id: SystemId<I, O>) -> impl Command<Result>
where where
I: SystemInput + Send + 'static, I: SystemInput + Send + 'static,
O: Send + 'static, O: 'static,
{ {
move |world: &mut World| -> Result { move |world: &mut World| -> Result {
world.unregister_system(system_id)?; world.unregister_system(system_id)?;

View File

@ -872,7 +872,7 @@ impl<'w, 's> Commands<'w, 's> {
/// ///
/// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError),
/// which will be handled by [logging the error at the `warn` level](warn). /// which will be handled by [logging the error at the `warn` level](warn).
pub fn run_system(&mut self, id: SystemId) { pub fn run_system<O: 'static>(&mut self, id: SystemId<(), O>) {
self.queue(command::run_system(id).handle_error_with(warn)); self.queue(command::run_system(id).handle_error_with(warn));
} }
@ -965,7 +965,7 @@ impl<'w, 's> Commands<'w, 's> {
) -> SystemId<I, O> ) -> SystemId<I, O>
where where
I: SystemInput + Send + 'static, I: SystemInput + Send + 'static,
O: Send + 'static, O: 'static,
{ {
let entity = self.spawn_empty().id(); let entity = self.spawn_empty().id();
let system = RegisteredSystem::<I, O>::new(Box::new(IntoSystem::into_system(system))); let system = RegisteredSystem::<I, O>::new(Box::new(IntoSystem::into_system(system)));
@ -990,7 +990,7 @@ impl<'w, 's> Commands<'w, 's> {
pub fn unregister_system<I, O>(&mut self, system_id: SystemId<I, O>) pub fn unregister_system<I, O>(&mut self, system_id: SystemId<I, O>)
where where
I: SystemInput + Send + 'static, I: SystemInput + Send + 'static,
O: Send + 'static, O: 'static,
{ {
self.queue(command::unregister_system(system_id).handle_error_with(warn)); self.queue(command::unregister_system(system_id).handle_error_with(warn));
} }
@ -1039,10 +1039,11 @@ impl<'w, 's> Commands<'w, 's> {
/// consider passing them in as inputs via [`Commands::run_system_cached_with`]. /// consider passing them in as inputs via [`Commands::run_system_cached_with`].
/// ///
/// If that's not an option, consider [`Commands::register_system`] instead. /// If that's not an option, consider [`Commands::register_system`] instead.
pub fn run_system_cached<M, S>(&mut self, system: S) pub fn run_system_cached<O, M, S>(&mut self, system: S)
where where
O: 'static,
M: 'static, M: 'static,
S: IntoSystem<(), (), M> + Send + 'static, S: IntoSystem<(), O, M> + Send + 'static,
{ {
self.queue(command::run_system_cached(system).handle_error_with(warn)); self.queue(command::run_system_cached(system).handle_error_with(warn));
} }
@ -1069,11 +1070,12 @@ impl<'w, 's> Commands<'w, 's> {
/// consider passing them in as inputs. /// consider passing them in as inputs.
/// ///
/// If that's not an option, consider [`Commands::register_system`] instead. /// If that's not an option, consider [`Commands::register_system`] instead.
pub fn run_system_cached_with<I, M, S>(&mut self, system: S, input: I::Inner<'static>) pub fn run_system_cached_with<I, O, M, S>(&mut self, system: S, input: I::Inner<'static>)
where where
I: SystemInput<Inner<'static>: Send> + Send + 'static, I: SystemInput<Inner<'static>: Send> + Send + 'static,
O: 'static,
M: 'static, M: 'static,
S: IntoSystem<I, (), M> + Send + 'static, S: IntoSystem<I, O, M> + Send + 'static,
{ {
self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); self.queue(command::run_system_cached_with(system, input).handle_error_with(warn));
} }

View File

@ -651,6 +651,19 @@ mod tests {
assert_eq!(output, NonCopy(3)); assert_eq!(output, NonCopy(3));
} }
#[test]
fn fallible_system() {
fn sys() -> Result<()> {
Err("error")?;
Ok(())
}
let mut world = World::new();
let fallible_system_id = world.register_system(sys);
let output = world.run_system(fallible_system_id);
assert!(matches!(output, Ok(Err(_))));
}
#[test] #[test]
fn exclusive_system() { fn exclusive_system() {
let mut world = World::new(); let mut world = World::new();
@ -751,19 +764,54 @@ mod tests {
assert!(matches!(output, Ok(x) if x == four())); assert!(matches!(output, Ok(x) if x == four()));
} }
#[test]
fn cached_fallible_system() {
fn sys() -> Result<()> {
Err("error")?;
Ok(())
}
let mut world = World::new();
let fallible_system_id = world.register_system_cached(sys);
let output = world.run_system(fallible_system_id);
assert!(matches!(output, Ok(Err(_))));
let output = world.run_system_cached(sys);
assert!(matches!(output, Ok(Err(_))));
let output = world.run_system_cached_with(sys, ());
assert!(matches!(output, Ok(Err(_))));
}
#[test] #[test]
fn cached_system_commands() { fn cached_system_commands() {
fn sys(mut counter: ResMut<Counter>) { fn sys(mut counter: ResMut<Counter>) {
counter.0 = 1; counter.0 += 1;
} }
let mut world = World::new(); let mut world = World::new();
world.insert_resource(Counter(0)); world.insert_resource(Counter(0));
world.commands().run_system_cached(sys); world.commands().run_system_cached(sys);
world.flush_commands(); world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 1); assert_eq!(world.resource::<Counter>().0, 1);
world.commands().run_system_cached_with(sys, ());
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 2);
}
#[test]
fn cached_fallible_system_commands() {
fn sys(mut counter: ResMut<Counter>) -> Result {
counter.0 += 1;
Ok(())
}
let mut world = World::new();
world.insert_resource(Counter(0));
world.commands().run_system_cached(sys);
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 1);
world.commands().run_system_cached_with(sys, ());
world.flush_commands();
assert_eq!(world.resource::<Counter>().0, 2);
} }
#[test] #[test]