Add RunSystem (#9366)

Add a `RunSystem` extension trait to allow for immediate execution of
systems on a `World` for debugging and/or testing purposes.

# Objective

Fixes #6184

Initially, I made this CL as `ApplyCommands`. After a discussion with
@cart , we decided a more generic implementation would be better to
support all systems. This is the new revised CL. Sorry for the long
delay! 😅

This CL allows users to do this:
```rust
use bevy::prelude::*;
use bevy::ecs::system::RunSystem;

struct T(usize);

impl Resource for T {}

fn system(In(n): In<usize>, mut commands: Commands) -> usize {
    commands.insert_resource(T(n));
    n + 1
}

let mut world = World::default();
let n = world.run_system_with(1, system);
assert_eq!(n, 2);
assert_eq!(world.resource::<T>().0, 1);
```

## Solution

This is implemented as a trait extension and not included in any
preludes to ensure it's being used consciously.
Internally, it just initializes and runs a systems, and applies any
deferred parameters all "in place".
The trait has 2 functions (one of which calls the other by default):
- `run_system_with` is the general implementation, which allows user to
pass system input parameters
- `run_system` is the ergonomic wrapper for systems with no input
parameter (to avoid having the user pass `()` as input).

~~Additionally, this trait is also implemented for `&mut App`. I added
this mainly for ergonomics (`app.run_system` vs.
`app.world.run_system`).~~ (Removed based on feedback)

---------

Co-authored-by: Pascal Hertleif <killercup@gmail.com>
This commit is contained in:
Zeenobit 2023-08-11 16:41:48 -04:00 committed by GitHub
parent 37915e1d93
commit 1e170d2e90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -8,6 +8,8 @@ use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Acce
use std::any::TypeId;
use std::borrow::Cow;
use super::IntoSystem;
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
///
/// Systems are functions with all arguments implementing
@ -164,3 +166,125 @@ impl Debug for dyn System<In = (), Out = ()> {
},)
}
}
/// Trait used to run a system immediately on a [`World`].
///
/// # Warning
/// This function is not an efficient method of running systems and its meant to be used as a utility
/// for testing and/or diagnostics.
///
/// # Usage
/// Typically, to test a system, or to extract specific diagnostics information from a world,
/// you'd need a [`Schedule`](crate::schedule::Schedule) to run the system. This can create redundant boilerplate code
/// when writing tests or trying to quickly iterate on debug specific systems.
///
/// For these situations, this function can be useful because it allows you to execute a system
/// immediately with some custom input and retrieve its output without requiring the necessary boilerplate.
///
/// # Examples
///
/// ## Immediate Command Execution
///
/// This usage is helpful when trying to test systems or functions that operate on [`Commands`](crate::system::Commands):
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::RunSystem;
/// let mut world = World::default();
/// let entity = world.run_system(|mut commands: Commands| {
/// commands.spawn_empty().id()
/// });
/// # assert!(world.get_entity(entity).is_some());
/// ```
///
/// ## Immediate Queries
///
/// This usage is helpful when trying to run an arbitrary query on a world for testing or debugging purposes:
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::RunSystem;
///
/// #[derive(Component)]
/// struct T(usize);
///
/// let mut world = World::default();
/// world.spawn(T(0));
/// world.spawn(T(1));
/// world.spawn(T(1));
/// let count = world.run_system(|query: Query<&T>| {
/// query.iter().filter(|t| t.0 == 1).count()
/// });
///
/// # assert_eq!(count, 2);
/// ```
///
/// Note that instead of closures you can also pass in regular functions as systems:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::RunSystem;
///
/// #[derive(Component)]
/// struct T(usize);
///
/// fn count(query: Query<&T>) -> usize {
/// query.iter().filter(|t| t.0 == 1).count()
/// }
///
/// let mut world = World::default();
/// world.spawn(T(0));
/// world.spawn(T(1));
/// world.spawn(T(1));
/// let count = world.run_system(count);
///
/// # assert_eq!(count, 2);
/// ```
pub trait RunSystem: Sized {
/// Runs a system and applies its deferred parameters.
fn run_system<T: IntoSystem<(), Out, Marker>, Out, Marker>(self, system: T) -> Out {
self.run_system_with((), system)
}
/// Runs a system with given input and applies its deferred parameters.
fn run_system_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
self,
input: In,
system: T,
) -> Out;
}
impl RunSystem for &mut World {
fn run_system_with<T: IntoSystem<In, Out, Marker>, In, Out, Marker>(
self,
input: In,
system: T,
) -> Out {
let mut system: T::System = IntoSystem::into_system(system);
system.initialize(self);
let out = system.run(input, self);
system.apply_deferred(self);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn run_system() {
struct T(usize);
impl Resource for T {}
fn system(In(n): In<usize>, mut commands: Commands) -> usize {
commands.insert_resource(T(n));
n + 1
}
let mut world = World::default();
let n = world.run_system_with(1, system);
assert_eq!(n, 2);
assert_eq!(world.resource::<T>().0, 1);
}
}