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,
/// 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
O: 'static,
M: 'static,
S: IntoSystem<(), (), M> + Send + 'static,
S: IntoSystem<(), O, M> + Send + 'static,
{
move |world: &mut World| -> Result {
world.run_system_cached(system)?;
@ -157,11 +158,15 @@ where
/// A [`Command`] that runs the given system with the given input value,
/// 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
I: SystemInput<Inner<'static>: Send> + Send + 'static,
O: 'static,
M: 'static,
S: IntoSystem<I, (), M> + Send + 'static,
S: IntoSystem<I, O, M> + Send + 'static,
{
move |world: &mut World| -> Result {
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>
where
I: SystemInput + Send + 'static,
O: Send + 'static,
O: 'static,
{
move |world: &mut World| -> Result {
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),
/// 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));
}
@ -965,7 +965,7 @@ impl<'w, 's> Commands<'w, 's> {
) -> SystemId<I, O>
where
I: SystemInput + Send + 'static,
O: Send + 'static,
O: 'static,
{
let entity = self.spawn_empty().id();
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>)
where
I: SystemInput + Send + 'static,
O: Send + 'static,
O: 'static,
{
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`].
///
/// 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
O: '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));
}
@ -1069,11 +1070,12 @@ impl<'w, 's> Commands<'w, 's> {
/// consider passing them in as inputs.
///
/// 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
I: SystemInput<Inner<'static>: Send> + Send + 'static,
O: '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));
}

View File

@ -651,6 +651,19 @@ mod tests {
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]
fn exclusive_system() {
let mut world = World::new();
@ -751,19 +764,54 @@ mod tests {
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]
fn cached_system_commands() {
fn sys(mut counter: ResMut<Counter>) {
counter.0 = 1;
counter.0 += 1;
}
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]
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]