bevy/crates/bevy_ecs/src/system/system_registry.rs
Chris Russell 5d1fe16bfd
Fix run_system for adapter systems wrapping exclusive systems (#18406)
# Objective

Fix panic in `run_system` when running an exclusive system wrapped in a
`PipeSystem` or `AdapterSystem`.

#18076 introduced a `System::run_without_applying_deferred` method. It
normally calls `System::run_unsafe`, but
`ExclusiveFunctionSystem::run_unsafe` panics, so it was overridden for
that type. Unfortunately, `PipeSystem::run_without_applying_deferred`
still calls `PipeSystem::run_unsafe`, which can then call
`ExclusiveFunctionSystem::run_unsafe` and panic.

## Solution

Make `ExclusiveFunctionSystem::run_unsafe` work instead of panicking.
Clarify the safety requirements that make this sound.

The alternative is to override `run_without_applying_deferred` in
`PipeSystem`, `CombinatorSystem`, `AdapterSystem`,
`InfallibleSystemWrapper`, and `InfallibleObserverWrapper`. That seems
like a lot of extra code just to preserve a confusing special case!

Remove some implementations of `System::run` that are no longer
necessary with this change. This slightly changes the behavior of
`PipeSystem` and `CombinatorSystem`: Currently `run` will call
`apply_deferred` on the first system before running the second, but
after this change it will only call it after *both* systems have run.
The new behavior is consistent with `run_unsafe` and
`run_without_applying_deferred`, and restores the behavior prior to
#11823.

The panic was originally necessary because [`run_unsafe` took
`&World`](https://github.com/bevyengine/bevy/pull/6083/files#diff-708dfc60ec5eef432b20a6f471357a7ea9bfb254dc2f918d5ed4a66deb0e85baR90).
Now that it takes `UnsafeWorldCell`, it is possible to make it work. See
also Cart's concerns at
https://github.com/bevyengine/bevy/pull/4166#discussion_r979140356,
although those also predate `UnsafeWorldCell`.

And see #6698 for a previous bug caused by this panic.
2025-03-26 13:40:42 +00:00

909 lines
31 KiB
Rust

#[cfg(feature = "bevy_reflect")]
use crate::reflect::ReflectComponent;
use crate::{
change_detection::Mut,
entity::Entity,
system::{input::SystemInput, BoxedSystem, IntoSystem},
world::World,
};
use alloc::boxed::Box;
use bevy_ecs_macros::{Component, Resource};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use core::marker::PhantomData;
use thiserror::Error;
/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
#[derive(Component)]
#[require(SystemIdMarker)]
pub(crate) struct RegisteredSystem<I, O> {
initialized: bool,
system: BoxedSystem<I, O>,
}
impl<I, O> RegisteredSystem<I, O> {
pub fn new(system: BoxedSystem<I, O>) -> Self {
RegisteredSystem {
initialized: false,
system,
}
}
}
/// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s.
#[derive(Component, Default)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component, Default))]
pub struct SystemIdMarker;
/// A system that has been removed from the registry.
/// It contains the system and whether or not it has been initialized.
///
/// This struct is returned by [`World::unregister_system`].
pub struct RemovedSystem<I = (), O = ()> {
initialized: bool,
system: BoxedSystem<I, O>,
}
impl<I, O> RemovedSystem<I, O> {
/// Is the system initialized?
/// A system is initialized the first time it's ran.
pub fn initialized(&self) -> bool {
self.initialized
}
/// The system removed from the storage.
pub fn system(self) -> BoxedSystem<I, O> {
self.system
}
}
/// An identifier for a registered system.
///
/// These are opaque identifiers, keyed to a specific [`World`],
/// and are created via [`World::register_system`].
pub struct SystemId<I: SystemInput = (), O = ()> {
pub(crate) entity: Entity,
pub(crate) marker: PhantomData<fn(I) -> O>,
}
impl<I: SystemInput, O> SystemId<I, O> {
/// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state.
///
/// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system
/// is really an entity with associated handler function.
///
/// For example, this is useful if you want to assign a name label to a system.
pub fn entity(self) -> Entity {
self.entity
}
/// Create [`SystemId`] from an [`Entity`]. Useful when you only have entity handles to avoid
/// adding extra components that have a [`SystemId`] everywhere. To run a system with this ID
/// - The entity must be a system
/// - The `I` + `O` types must be correct
pub fn from_entity(entity: Entity) -> Self {
Self {
entity,
marker: PhantomData,
}
}
}
impl<I: SystemInput, O> Eq for SystemId<I, O> {}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I: SystemInput, O> Copy for SystemId<I, O> {}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I: SystemInput, O> Clone for SystemId<I, O> {
fn clone(&self) -> Self {
*self
}
}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I: SystemInput, O> PartialEq for SystemId<I, O> {
fn eq(&self, other: &Self) -> bool {
self.entity == other.entity && self.marker == other.marker
}
}
// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
impl<I: SystemInput, O> core::hash::Hash for SystemId<I, O> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.entity.hash(state);
}
}
impl<I: SystemInput, O> core::fmt::Debug for SystemId<I, O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("SystemId").field(&self.entity).finish()
}
}
/// A cached [`SystemId`] distinguished by the unique function type of its system.
///
/// This resource is inserted by [`World::register_system_cached`].
#[derive(Resource)]
pub struct CachedSystemId<S> {
/// The cached `SystemId` as an `Entity`.
pub entity: Entity,
_marker: PhantomData<fn() -> S>,
}
impl<S> CachedSystemId<S> {
/// Creates a new `CachedSystemId` struct given a `SystemId`.
pub fn new<I: SystemInput, O>(id: SystemId<I, O>) -> Self {
Self {
entity: id.entity(),
_marker: PhantomData,
}
}
}
impl World {
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
///
/// It's possible to register multiple copies of the same system by calling this function
/// multiple times. If that's not what you want, consider using [`World::register_system_cached`]
/// instead.
///
/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),
/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.
/// This allows for running systems in a pushed-based fashion.
/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases
/// due to its better performance and ability to run non-conflicting systems simultaneously.
pub fn register_system<I, O, M>(
&mut self,
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
}
/// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].
///
/// This is useful if the [`IntoSystem`] implementor has already been turned into a
/// [`System`](crate::system::System) trait object and put in a [`Box`].
pub fn register_boxed_system<I, O>(&mut self, system: BoxedSystem<I, O>) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
let entity = self.spawn(RegisteredSystem::new(system)).id();
SystemId::from_entity(entity)
}
/// Removes a registered system and returns the system, if it exists.
/// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors.
/// Re-adding the removed system will register it on a new [`SystemId`].
///
/// If no system corresponds to the given [`SystemId`], this method returns an error.
/// Systems are also not allowed to remove themselves, this returns an error too.
pub fn unregister_system<I, O>(
&mut self,
id: SystemId<I, O>,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
match self.get_entity_mut(id.entity) {
Ok(mut entity) => {
let registered_system = entity
.take::<RegisteredSystem<I, O>>()
.ok_or(RegisteredSystemError::SelfRemove(id))?;
entity.despawn();
Ok(RemovedSystem {
initialized: registered_system.initialized,
system: registered_system.system,
})
}
Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
}
}
/// Run stored systems by their [`SystemId`].
/// Before running a system, it must first be registered.
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
/// because it keeps local state between calls and change detection works correctly.
///
/// Also runs any queued-up commands.
///
/// In order to run a chained system with an input, use [`World::run_system_with`] instead.
///
/// # Examples
///
/// ## Running a system
///
/// ```
/// # use bevy_ecs::prelude::*;
/// fn increment(mut counter: Local<u8>) {
/// *counter += 1;
/// println!("{}", *counter);
/// }
///
/// let mut world = World::default();
/// let counter_one = world.register_system(increment);
/// let counter_two = world.register_system(increment);
/// world.run_system(counter_one); // -> 1
/// world.run_system(counter_one); // -> 2
/// world.run_system(counter_two); // -> 1
/// ```
///
/// ## Change detection
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Resource, Default)]
/// struct ChangeDetector;
///
/// let mut world = World::default();
/// world.init_resource::<ChangeDetector>();
/// let detector = world.register_system(|change_detector: ResMut<ChangeDetector>| {
/// if change_detector.is_changed() {
/// println!("Something happened!");
/// } else {
/// println!("Nothing happened.");
/// }
/// });
///
/// // Resources are changed when they are first added
/// let _ = world.run_system(detector); // -> Something happened!
/// let _ = world.run_system(detector); // -> Nothing happened.
/// world.resource_mut::<ChangeDetector>().set_changed();
/// let _ = world.run_system(detector); // -> Something happened!
/// ```
///
/// ## Getting system output
///
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Resource)]
/// struct PlayerScore(i32);
///
/// #[derive(Resource)]
/// struct OpponentScore(i32);
///
/// fn get_player_score(player_score: Res<PlayerScore>) -> i32 {
/// player_score.0
/// }
///
/// fn get_opponent_score(opponent_score: Res<OpponentScore>) -> i32 {
/// opponent_score.0
/// }
///
/// let mut world = World::default();
/// world.insert_resource(PlayerScore(3));
/// world.insert_resource(OpponentScore(2));
///
/// let scoring_systems = [
/// ("player", world.register_system(get_player_score)),
/// ("opponent", world.register_system(get_opponent_score)),
/// ];
///
/// for (label, scoring_system) in scoring_systems {
/// println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded"));
/// }
/// ```
pub fn run_system<O: 'static>(
&mut self,
id: SystemId<(), O>,
) -> Result<O, RegisteredSystemError<(), O>> {
self.run_system_with(id, ())
}
/// Run a stored chained system by its [`SystemId`], providing an input value.
/// Before running a system, it must first be registered.
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
///
/// Also runs any queued-up commands.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// fn increment(In(increment_by): In<u8>, mut counter: Local<u8>) -> u8 {
/// *counter += increment_by;
/// *counter
/// }
///
/// let mut world = World::default();
/// let counter_one = world.register_system(increment);
/// let counter_two = world.register_system(increment);
/// assert_eq!(world.run_system_with(counter_one, 1).unwrap(), 1);
/// assert_eq!(world.run_system_with(counter_one, 20).unwrap(), 21);
/// assert_eq!(world.run_system_with(counter_two, 30).unwrap(), 30);
/// ```
///
/// See [`World::run_system`] for more examples.
pub fn run_system_with<I, O>(
&mut self,
id: SystemId<I, O>,
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
{
// Lookup
let mut entity = self
.get_entity_mut(id.entity)
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
// Take ownership of system trait object
let RegisteredSystem {
mut initialized,
mut system,
} = entity
.take::<RegisteredSystem<I, O>>()
.ok_or(RegisteredSystemError::Recursive(id))?;
// Run the system
if !initialized {
system.initialize(self);
initialized = true;
}
let result = if system.validate_param(self).is_ok() {
// Wait to run the commands until the system is available again.
// This is needed so the systems can recursively run themselves.
let ret = system.run_without_applying_deferred(input, self);
system.queue_deferred(self.into());
Ok(ret)
} else {
// TODO: do we want to differentiate between failed validation and skipped systems?
// Do we want to better unify this with system error handling?
Err(RegisteredSystemError::InvalidParams(id))
};
// Return ownership of system trait object (if entity still exists)
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
initialized,
system,
});
}
// Run any commands enqueued by the system
self.flush();
result
}
/// Registers a system or returns its cached [`SystemId`].
///
/// If you want to run the system immediately and you don't need its `SystemId`, see
/// [`World::run_system_cached`].
///
/// The first time this function is called for a particular system, it will register it and
/// store its [`SystemId`] in a [`CachedSystemId`] resource for later. If you would rather
/// manage the `SystemId` yourself, or register multiple copies of the same system, use
/// [`World::register_system`] instead.
///
/// # Limitations
///
/// This function only accepts ZST (zero-sized) systems to guarantee that any two systems of
/// the same type must be equal. This means that closures that capture the environment, and
/// function pointers, are not accepted.
///
/// If you want to access values from the environment within a system, consider passing them in
/// as inputs via [`World::run_system_cached_with`]. If that's not an option, consider
/// [`World::register_system`] instead.
pub fn register_system_cached<I, O, M, S>(&mut self, system: S) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
const {
assert!(
size_of::<S>() == 0,
"Non-ZST systems (e.g. capturing closures, function pointers) cannot be cached.",
);
}
if !self.contains_resource::<CachedSystemId<S>>() {
let id = self.register_system(system);
self.insert_resource(CachedSystemId::<S>::new(id));
return id;
}
self.resource_scope(|world, mut id: Mut<CachedSystemId<S>>| {
if let Ok(mut entity) = world.get_entity_mut(id.entity) {
if !entity.contains::<RegisteredSystem<I, O>>() {
entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system(
system,
))));
}
} else {
id.entity = world.register_system(system).entity();
}
SystemId::from_entity(id.entity)
})
}
/// Removes a cached system and its [`CachedSystemId`] resource.
///
/// See [`World::register_system_cached`] for more information.
pub fn unregister_system_cached<I, O, M, S>(
&mut self,
_system: S,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self
.remove_resource::<CachedSystemId<S>>()
.ok_or(RegisteredSystemError::SystemNotCached)?;
self.unregister_system(SystemId::<I, O>::from_entity(id.entity))
}
/// Runs a cached system, registering it if necessary.
///
/// See [`World::register_system_cached`] for more information.
pub fn run_system_cached<O: 'static, M, S: IntoSystem<(), O, M> + 'static>(
&mut self,
system: S,
) -> Result<O, RegisteredSystemError<(), O>> {
self.run_system_cached_with(system, ())
}
/// Runs a cached system with an input, registering it if necessary.
///
/// See [`World::register_system_cached`] for more information.
pub fn run_system_cached_with<I, O, M, S>(
&mut self,
system: S,
input: I::Inner<'_>,
) -> Result<O, RegisteredSystemError<I, O>>
where
I: SystemInput + 'static,
O: 'static,
S: IntoSystem<I, O, M> + 'static,
{
let id = self.register_system_cached(system);
self.run_system_with(id, input)
}
}
/// An operation with stored systems failed.
#[derive(Error)]
pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {
/// A system was run by id, but no system with that id was found.
///
/// Did you forget to register it?
#[error("System {0:?} was not registered")]
SystemIdNotRegistered(SystemId<I, O>),
/// A cached system was removed by value, but no system with its type was found.
///
/// Did you forget to register it?
#[error("Cached system was not found")]
SystemNotCached,
/// A system tried to run itself recursively.
#[error("System {0:?} tried to run itself recursively")]
Recursive(SystemId<I, O>),
/// A system tried to remove itself.
#[error("System {0:?} tried to remove itself")]
SelfRemove(SystemId<I, O>),
/// System could not be run due to parameters that failed validation.
///
/// This can occur because the data required by the system was not present in the world.
#[error("The data required by the system {0:?} was not found in the world and the system did not run due to failed parameter validation.")]
InvalidParams(SystemId<I, O>),
}
impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::SystemIdNotRegistered(arg0) => {
f.debug_tuple("SystemIdNotRegistered").field(arg0).finish()
}
Self::SystemNotCached => write!(f, "SystemNotCached"),
Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(),
Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),
Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(),
}
}
}
#[cfg(test)]
mod tests {
use core::cell::Cell;
use bevy_utils::default;
use crate::{prelude::*, system::SystemId};
#[derive(Resource, Default, PartialEq, Debug)]
struct Counter(u8);
#[test]
fn change_detection() {
#[derive(Resource, Default)]
struct ChangeDetector;
fn count_up_iff_changed(
mut counter: ResMut<Counter>,
change_detector: ResMut<ChangeDetector>,
) {
if change_detector.is_changed() {
counter.0 += 1;
}
}
let mut world = World::new();
world.init_resource::<ChangeDetector>();
world.init_resource::<Counter>();
assert_eq!(*world.resource::<Counter>(), Counter(0));
// Resources are changed when they are first added.
let id = world.register_system(count_up_iff_changed);
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
// Nothing changed
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
// Making a change
world.resource_mut::<ChangeDetector>().set_changed();
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[test]
fn local_variables() {
// The `Local` begins at the default value of 0
fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {
counter.0 += last_counter.0 .0;
last_counter.0 .0 = counter.0;
}
let mut world = World::new();
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
let id = world.register_system(doubling);
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(1));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(4));
world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(8));
}
#[test]
fn input_values() {
// Verify that a non-Copy, non-Clone type can be passed in.
struct NonCopy(u8);
fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {
counter.0 += increment_by;
}
let mut world = World::new();
let id = world.register_system(increment_sys);
// Insert the resource after registering the system.
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(3));
world
.run_system_with(id, NonCopy(20))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(23));
world
.run_system_with(id, NonCopy(1))
.expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(24));
}
#[test]
fn output_values() {
// Verify that a non-Copy, non-Clone type can be returned.
#[derive(Eq, PartialEq, Debug)]
struct NonCopy(u8);
fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {
counter.0 += 1;
NonCopy(counter.0)
}
let mut world = World::new();
let id = world.register_system(increment_sys);
// Insert the resource after registering the system.
world.insert_resource(Counter(1));
assert_eq!(*world.resource::<Counter>(), Counter(1));
let output = world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(2));
assert_eq!(output, NonCopy(2));
let output = world.run_system(id).expect("system runs successfully");
assert_eq!(*world.resource::<Counter>(), Counter(3));
assert_eq!(output, NonCopy(3));
}
#[test]
fn exclusive_system() {
let mut world = World::new();
let exclusive_system_id = world.register_system(|world: &mut World| {
world.spawn_empty();
});
let entity_count = world.entities.len();
let _ = world.run_system(exclusive_system_id);
assert_eq!(world.entities.len(), entity_count + 1);
}
#[test]
fn nested_systems() {
use crate::system::SystemId;
#[derive(Component)]
struct Callback(SystemId);
fn nested(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
commands.run_system(callback.0);
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let increment_two = world.register_system(|mut counter: ResMut<Counter>| {
counter.0 += 2;
});
let increment_three = world.register_system(|mut counter: ResMut<Counter>| {
counter.0 += 3;
});
let nested_id = world.register_system(nested);
world.spawn(Callback(increment_two));
world.spawn(Callback(increment_three));
let _ = world.run_system(nested_id);
assert_eq!(*world.resource::<Counter>(), Counter(5));
}
#[test]
fn nested_systems_with_inputs() {
use crate::system::SystemId;
#[derive(Component)]
struct Callback(SystemId<In<u8>>, u8);
fn nested(query: Query<&Callback>, mut commands: Commands) {
for callback in query.iter() {
commands.run_system_with(callback.0, callback.1);
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let increment_by =
world.register_system(|In(amt): In<u8>, mut counter: ResMut<Counter>| {
counter.0 += amt;
});
let nested_id = world.register_system(nested);
world.spawn(Callback(increment_by, 2));
world.spawn(Callback(increment_by, 3));
let _ = world.run_system(nested_id);
assert_eq!(*world.resource::<Counter>(), Counter(5));
}
#[test]
fn cached_system() {
use crate::system::RegisteredSystemError;
fn four() -> i32 {
4
}
let mut world = World::new();
let old = world.register_system_cached(four);
let new = world.register_system_cached(four);
assert_eq!(old, new);
let result = world.unregister_system_cached(four);
assert!(result.is_ok());
let new = world.register_system_cached(four);
assert_ne!(old, new);
let output = world.run_system(old);
assert!(matches!(
output,
Err(RegisteredSystemError::SystemIdNotRegistered(x)) if x == old,
));
let output = world.run_system(new);
assert!(matches!(output, Ok(x) if x == four()));
let output = world.run_system_cached(four);
assert!(matches!(output, Ok(x) if x == four()));
let output = world.run_system_cached_with(four, ());
assert!(matches!(output, Ok(x) if x == four()));
}
#[test]
fn cached_system_commands() {
fn sys(mut counter: ResMut<Counter>) {
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);
}
#[test]
fn cached_system_adapters() {
fn four() -> i32 {
4
}
fn double(In(i): In<i32>) -> i32 {
i * 2
}
let mut world = World::new();
let output = world.run_system_cached(four.pipe(double));
assert!(matches!(output, Ok(8)));
let output = world.run_system_cached(four.map(|i| i * 2));
assert!(matches!(output, Ok(8)));
}
#[test]
fn cached_system_into_same_system_type() {
use crate::error::Result;
struct Foo;
impl IntoSystem<(), Result<()>, ()> for Foo {
type System = ApplyDeferred;
fn into_system(_: Self) -> Self::System {
ApplyDeferred
}
}
struct Bar;
impl IntoSystem<(), Result<()>, ()> for Bar {
type System = ApplyDeferred;
fn into_system(_: Self) -> Self::System {
ApplyDeferred
}
}
let mut world = World::new();
let foo1 = world.register_system_cached(Foo);
let foo2 = world.register_system_cached(Foo);
let bar1 = world.register_system_cached(Bar);
let bar2 = world.register_system_cached(Bar);
// The `S: IntoSystem` types are different, so they should be cached
// as separate systems, even though the `<S as IntoSystem>::System`
// types / values are the same (`ApplyDeferred`).
assert_ne!(foo1, bar1);
// But if the `S: IntoSystem` types are the same, they'll be cached
// as the same system.
assert_eq!(foo1, foo2);
assert_eq!(bar1, bar2);
}
#[test]
fn system_with_input_ref() {
fn with_ref(InRef(input): InRef<u8>, mut counter: ResMut<Counter>) {
counter.0 += *input;
}
let mut world = World::new();
world.insert_resource(Counter(0));
let id = world.register_system(with_ref);
world.run_system_with(id, &2).unwrap();
assert_eq!(*world.resource::<Counter>(), Counter(2));
}
#[test]
fn system_with_input_mut() {
#[derive(Event)]
struct MyEvent {
cancelled: bool,
}
fn post(InMut(event): InMut<MyEvent>, counter: ResMut<Counter>) {
if counter.0 > 0 {
event.cancelled = true;
}
}
let mut world = World::new();
world.insert_resource(Counter(0));
let post_system = world.register_system(post);
let mut event = MyEvent { cancelled: false };
world.run_system_with(post_system, &mut event).unwrap();
assert!(!event.cancelled);
world.resource_mut::<Counter>().0 = 1;
world.run_system_with(post_system, &mut event).unwrap();
assert!(event.cancelled);
}
#[test]
fn run_system_invalid_params() {
use crate::system::RegisteredSystemError;
struct T;
impl Resource for T {}
fn system(_: Res<T>) {}
let mut world = World::new();
let id = world.register_system(system);
// This fails because `T` has not been added to the world yet.
let result = world.run_system(id);
assert!(matches!(
result,
Err(RegisteredSystemError::InvalidParams(_))
));
}
#[test]
fn run_system_recursive() {
std::thread_local! {
static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };
static SYSTEM_ID: Cell<Option<SystemId>> = default();
}
fn system(mut commands: Commands) {
let count = INVOCATIONS_LEFT.get() - 1;
INVOCATIONS_LEFT.set(count);
if count > 0 {
commands.run_system(SYSTEM_ID.get().unwrap());
}
}
let mut world = World::new();
let id = world.register_system(system);
SYSTEM_ID.set(Some(id));
world.run_system(id).unwrap();
assert_eq!(INVOCATIONS_LEFT.get(), 0);
}
#[test]
fn run_system_exclusive_adapters() {
let mut world = World::new();
fn system(_: &mut World) {}
world.run_system_cached(system).unwrap();
world.run_system_cached(system.pipe(system)).unwrap();
world.run_system_cached(system.map(|()| {})).unwrap();
}
}