Path: blob/main/crates/bevy_ecs/src/system/system_registry.rs
6604 views
#[cfg(feature = "hotpatching")]1use crate::{change_detection::DetectChanges, HotPatchChanges};2use crate::{3change_detection::Mut,4entity::Entity,5entity_disabling::Internal,6error::BevyError,7system::{8input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError,9},10world::World,11};12use alloc::boxed::Box;13use bevy_ecs_macros::{Component, Resource};14use bevy_utils::prelude::DebugName;15use core::{any::TypeId, marker::PhantomData};16use thiserror::Error;1718/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.19#[derive(Component)]20#[require(SystemIdMarker = SystemIdMarker::typed_system_id_marker::<I, O>(), Internal)]21pub(crate) struct RegisteredSystem<I, O> {22initialized: bool,23system: BoxedSystem<I, O>,24}2526impl<I, O> RegisteredSystem<I, O> {27pub fn new(system: BoxedSystem<I, O>) -> Self {28RegisteredSystem {29initialized: false,30system,31}32}33}3435#[derive(Debug, Clone)]36struct TypeIdAndName {37type_id: TypeId,38name: DebugName,39}4041impl TypeIdAndName {42fn new<T: 'static>() -> Self {43Self {44type_id: TypeId::of::<T>(),45name: DebugName::type_name::<T>(),46}47}48}4950impl Default for TypeIdAndName {51fn default() -> Self {52Self {53type_id: TypeId::of::<()>(),54name: DebugName::type_name::<()>(),55}56}57}5859/// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s.60#[derive(Debug, Default, Clone, Component)]61pub struct SystemIdMarker {62input_type_id: TypeIdAndName,63output_type_id: TypeIdAndName,64}6566impl SystemIdMarker {67fn typed_system_id_marker<I: 'static, O: 'static>() -> Self {68Self {69input_type_id: TypeIdAndName::new::<I>(),70output_type_id: TypeIdAndName::new::<O>(),71}72}73}7475/// A system that has been removed from the registry.76/// It contains the system and whether or not it has been initialized.77///78/// This struct is returned by [`World::unregister_system`].79pub struct RemovedSystem<I = (), O = ()> {80initialized: bool,81system: BoxedSystem<I, O>,82}8384impl<I, O> RemovedSystem<I, O> {85/// Is the system initialized?86/// A system is initialized the first time it's ran.87pub fn initialized(&self) -> bool {88self.initialized89}9091/// The system removed from the storage.92pub fn system(self) -> BoxedSystem<I, O> {93self.system94}95}9697/// An identifier for a registered system.98///99/// These are opaque identifiers, keyed to a specific [`World`],100/// and are created via [`World::register_system`].101pub struct SystemId<I: SystemInput = (), O = ()> {102pub(crate) entity: Entity,103pub(crate) marker: PhantomData<fn(I) -> O>,104}105106impl<I: SystemInput, O> SystemId<I, O> {107/// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state.108///109/// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system110/// is really an entity with associated handler function.111///112/// For example, this is useful if you want to assign a name label to a system.113pub fn entity(self) -> Entity {114self.entity115}116117/// Create [`SystemId`] from an [`Entity`]. Useful when you only have entity handles to avoid118/// adding extra components that have a [`SystemId`] everywhere. To run a system with this ID119/// - The entity must be a system120/// - The `I` + `O` types must be correct121pub fn from_entity(entity: Entity) -> Self {122Self {123entity,124marker: PhantomData,125}126}127}128129impl<I: SystemInput, O> Eq for SystemId<I, O> {}130131// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.132impl<I: SystemInput, O> Copy for SystemId<I, O> {}133134// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.135impl<I: SystemInput, O> Clone for SystemId<I, O> {136fn clone(&self) -> Self {137*self138}139}140141// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.142impl<I: SystemInput, O> PartialEq for SystemId<I, O> {143fn eq(&self, other: &Self) -> bool {144self.entity == other.entity && self.marker == other.marker145}146}147148// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.149impl<I: SystemInput, O> core::hash::Hash for SystemId<I, O> {150fn hash<H: core::hash::Hasher>(&self, state: &mut H) {151self.entity.hash(state);152}153}154155impl<I: SystemInput, O> core::fmt::Debug for SystemId<I, O> {156fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {157f.debug_tuple("SystemId").field(&self.entity).finish()158}159}160161/// A cached [`SystemId`] distinguished by the unique function type of its system.162///163/// This resource is inserted by [`World::register_system_cached`].164#[derive(Resource)]165pub struct CachedSystemId<S> {166/// The cached `SystemId` as an `Entity`.167pub entity: Entity,168_marker: PhantomData<fn() -> S>,169}170171impl<S> CachedSystemId<S> {172/// Creates a new `CachedSystemId` struct given a `SystemId`.173pub fn new<I: SystemInput, O>(id: SystemId<I, O>) -> Self {174Self {175entity: id.entity(),176_marker: PhantomData,177}178}179}180181impl World {182/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].183///184/// It's possible to register multiple copies of the same system by calling this function185/// multiple times. If that's not what you want, consider using [`World::register_system_cached`]186/// instead.187///188/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),189/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.190/// This allows for running systems in a pushed-based fashion.191/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases192/// due to its better performance and ability to run non-conflicting systems simultaneously.193pub fn register_system<I, O, M>(194&mut self,195system: impl IntoSystem<I, O, M> + 'static,196) -> SystemId<I, O>197where198I: SystemInput + 'static,199O: 'static,200{201self.register_boxed_system(Box::new(IntoSystem::into_system(system)))202}203204/// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].205///206/// This is useful if the [`IntoSystem`] implementor has already been turned into a207/// [`System`](crate::system::System) trait object and put in a [`Box`].208pub fn register_boxed_system<I, O>(&mut self, system: BoxedSystem<I, O>) -> SystemId<I, O>209where210I: SystemInput + 'static,211O: 'static,212{213let entity = self.spawn(RegisteredSystem::new(system)).id();214SystemId::from_entity(entity)215}216217/// Removes a registered system and returns the system, if it exists.218/// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors.219/// Re-adding the removed system will register it on a new [`SystemId`].220///221/// If no system corresponds to the given [`SystemId`], this method returns an error.222/// Systems are also not allowed to remove themselves, this returns an error too.223pub fn unregister_system<I, O>(224&mut self,225id: SystemId<I, O>,226) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>227where228I: SystemInput + 'static,229O: 'static,230{231match self.get_entity_mut(id.entity) {232Ok(mut entity) => {233let registered_system = entity234.take::<RegisteredSystem<I, O>>()235.ok_or(RegisteredSystemError::SelfRemove(id))?;236entity.despawn();237Ok(RemovedSystem {238initialized: registered_system.initialized,239system: registered_system.system,240})241}242Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)),243}244}245246/// Run stored systems by their [`SystemId`].247/// Before running a system, it must first be registered.248/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].249/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),250/// because it keeps local state between calls and change detection works correctly.251///252/// Also runs any queued-up commands.253///254/// In order to run a chained system with an input, use [`World::run_system_with`] instead.255///256/// # Examples257///258/// ## Running a system259///260/// ```261/// # use bevy_ecs::prelude::*;262/// fn increment(mut counter: Local<u8>) {263/// *counter += 1;264/// println!("{}", *counter);265/// }266///267/// let mut world = World::default();268/// let counter_one = world.register_system(increment);269/// let counter_two = world.register_system(increment);270/// world.run_system(counter_one); // -> 1271/// world.run_system(counter_one); // -> 2272/// world.run_system(counter_two); // -> 1273/// ```274///275/// ## Change detection276///277/// ```278/// # use bevy_ecs::prelude::*;279/// #[derive(Resource, Default)]280/// struct ChangeDetector;281///282/// let mut world = World::default();283/// world.init_resource::<ChangeDetector>();284/// let detector = world.register_system(|change_detector: ResMut<ChangeDetector>| {285/// if change_detector.is_changed() {286/// println!("Something happened!");287/// } else {288/// println!("Nothing happened.");289/// }290/// });291///292/// // Resources are changed when they are first added293/// let _ = world.run_system(detector); // -> Something happened!294/// let _ = world.run_system(detector); // -> Nothing happened.295/// world.resource_mut::<ChangeDetector>().set_changed();296/// let _ = world.run_system(detector); // -> Something happened!297/// ```298///299/// ## Getting system output300///301/// ```302/// # use bevy_ecs::prelude::*;303///304/// #[derive(Resource)]305/// struct PlayerScore(i32);306///307/// #[derive(Resource)]308/// struct OpponentScore(i32);309///310/// fn get_player_score(player_score: Res<PlayerScore>) -> i32 {311/// player_score.0312/// }313///314/// fn get_opponent_score(opponent_score: Res<OpponentScore>) -> i32 {315/// opponent_score.0316/// }317///318/// let mut world = World::default();319/// world.insert_resource(PlayerScore(3));320/// world.insert_resource(OpponentScore(2));321///322/// let scoring_systems = [323/// ("player", world.register_system(get_player_score)),324/// ("opponent", world.register_system(get_opponent_score)),325/// ];326///327/// for (label, scoring_system) in scoring_systems {328/// println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded"));329/// }330/// ```331pub fn run_system<O: 'static>(332&mut self,333id: SystemId<(), O>,334) -> Result<O, RegisteredSystemError<(), O>> {335self.run_system_with(id, ())336}337338/// Run a stored chained system by its [`SystemId`], providing an input value.339/// Before running a system, it must first be registered.340/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].341///342/// Also runs any queued-up commands.343///344/// # Examples345///346/// ```347/// # use bevy_ecs::prelude::*;348/// fn increment(In(increment_by): In<u8>, mut counter: Local<u8>) -> u8 {349/// *counter += increment_by;350/// *counter351/// }352///353/// let mut world = World::default();354/// let counter_one = world.register_system(increment);355/// let counter_two = world.register_system(increment);356/// assert_eq!(world.run_system_with(counter_one, 1).unwrap(), 1);357/// assert_eq!(world.run_system_with(counter_one, 20).unwrap(), 21);358/// assert_eq!(world.run_system_with(counter_two, 30).unwrap(), 30);359/// ```360///361/// See [`World::run_system`] for more examples.362pub fn run_system_with<I, O>(363&mut self,364id: SystemId<I, O>,365input: I::Inner<'_>,366) -> Result<O, RegisteredSystemError<I, O>>367where368I: SystemInput + 'static,369O: 'static,370{371// Lookup372let mut entity = self373.get_entity_mut(id.entity)374.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;375376// Take ownership of system trait object377let Some(RegisteredSystem {378mut initialized,379mut system,380}) = entity.take::<RegisteredSystem<I, O>>()381else {382let Some(system_id_marker) = entity.get::<SystemIdMarker>() else {383return Err(RegisteredSystemError::SystemIdNotRegistered(id));384};385if system_id_marker.input_type_id.type_id != TypeId::of::<I>()386|| system_id_marker.output_type_id.type_id != TypeId::of::<O>()387{388return Err(RegisteredSystemError::IncorrectType(389id,390system_id_marker.clone(),391));392}393return Err(RegisteredSystemError::Recursive(id));394};395396// Initialize the system397if !initialized {398system.initialize(self);399initialized = true;400}401402// refresh hotpatches for stored systems403#[cfg(feature = "hotpatching")]404if self405.get_resource_ref::<HotPatchChanges>()406.map(|r| r.last_changed())407.unwrap_or_default()408.is_newer_than(system.get_last_run(), self.change_tick())409{410system.refresh_hotpatch();411}412413// Wait to run the commands until the system is available again.414// This is needed so the systems can recursively run themselves.415let result = system.run_without_applying_deferred(input, self);416system.queue_deferred(self.into());417418// Return ownership of system trait object (if entity still exists)419if let Ok(mut entity) = self.get_entity_mut(id.entity) {420entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {421initialized,422system,423});424}425426// Run any commands enqueued by the system427self.flush();428Ok(result?)429}430431/// Registers a system or returns its cached [`SystemId`].432///433/// If you want to run the system immediately and you don't need its `SystemId`, see434/// [`World::run_system_cached`].435///436/// The first time this function is called for a particular system, it will register it and437/// store its [`SystemId`] in a [`CachedSystemId`] resource for later. If you would rather438/// manage the `SystemId` yourself, or register multiple copies of the same system, use439/// [`World::register_system`] instead.440///441/// # Limitations442///443/// This function only accepts ZST (zero-sized) systems to guarantee that any two systems of444/// the same type must be equal. This means that closures that capture the environment, and445/// function pointers, are not accepted.446///447/// If you want to access values from the environment within a system, consider passing them in448/// as inputs via [`World::run_system_cached_with`]. If that's not an option, consider449/// [`World::register_system`] instead.450pub fn register_system_cached<I, O, M, S>(&mut self, system: S) -> SystemId<I, O>451where452I: SystemInput + 'static,453O: 'static,454S: IntoSystem<I, O, M> + 'static,455{456const {457assert!(458size_of::<S>() == 0,459"Non-ZST systems (e.g. capturing closures, function pointers) cannot be cached.",460);461}462463if !self.contains_resource::<CachedSystemId<S>>() {464let id = self.register_system(system);465self.insert_resource(CachedSystemId::<S>::new(id));466return id;467}468469self.resource_scope(|world, mut id: Mut<CachedSystemId<S>>| {470if let Ok(mut entity) = world.get_entity_mut(id.entity) {471if !entity.contains::<RegisteredSystem<I, O>>() {472entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system(473system,474))));475}476} else {477id.entity = world.register_system(system).entity();478}479SystemId::from_entity(id.entity)480})481}482483/// Removes a cached system and its [`CachedSystemId`] resource.484///485/// See [`World::register_system_cached`] for more information.486pub fn unregister_system_cached<I, O, M, S>(487&mut self,488_system: S,489) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>490where491I: SystemInput + 'static,492O: 'static,493S: IntoSystem<I, O, M> + 'static,494{495let id = self496.remove_resource::<CachedSystemId<S>>()497.ok_or(RegisteredSystemError::SystemNotCached)?;498self.unregister_system(SystemId::<I, O>::from_entity(id.entity))499}500501/// Runs a cached system, registering it if necessary.502///503/// See [`World::register_system_cached`] for more information.504pub fn run_system_cached<O: 'static, M, S: IntoSystem<(), O, M> + 'static>(505&mut self,506system: S,507) -> Result<O, RegisteredSystemError<(), O>> {508self.run_system_cached_with(system, ())509}510511/// Runs a cached system with an input, registering it if necessary.512///513/// See [`World::register_system_cached`] for more information.514pub fn run_system_cached_with<I, O, M, S>(515&mut self,516system: S,517input: I::Inner<'_>,518) -> Result<O, RegisteredSystemError<I, O>>519where520I: SystemInput + 'static,521O: 'static,522S: IntoSystem<I, O, M> + 'static,523{524let id = self.register_system_cached(system);525self.run_system_with(id, input)526}527}528529/// An operation with stored systems failed.530#[derive(Error)]531pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {532/// A system was run by id, but no system with that id was found.533///534/// Did you forget to register it?535#[error("System {0:?} was not registered")]536SystemIdNotRegistered(SystemId<I, O>),537/// A cached system was removed by value, but no system with its type was found.538///539/// Did you forget to register it?540#[error("Cached system was not found")]541SystemNotCached,542/// A system tried to run itself recursively.543#[error("System {0:?} tried to run itself recursively")]544Recursive(SystemId<I, O>),545/// A system tried to remove itself.546#[error("System {0:?} tried to remove itself")]547SelfRemove(SystemId<I, O>),548/// System could not be run due to parameters that failed validation.549/// This is not considered an error.550#[error("System did not run due to failed parameter validation: {0}")]551Skipped(SystemParamValidationError),552/// System returned an error or failed required parameter validation.553#[error("System returned error: {0}")]554Failed(BevyError),555/// [`SystemId`] had different input and/or output types than [`SystemIdMarker`]556#[error("Could not get system from `{}`, entity was `SystemId<{}, {}>`", DebugName::type_name::<SystemId<I, O>>(), .1.input_type_id.name, .1.output_type_id.name)]557IncorrectType(SystemId<I, O>, SystemIdMarker),558}559560impl<I: SystemInput, O> From<RunSystemError> for RegisteredSystemError<I, O> {561fn from(value: RunSystemError) -> Self {562match value {563RunSystemError::Skipped(err) => Self::Skipped(err),564RunSystemError::Failed(err) => Self::Failed(err),565}566}567}568569impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {570fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {571match self {572Self::SystemIdNotRegistered(arg0) => {573f.debug_tuple("SystemIdNotRegistered").field(arg0).finish()574}575Self::SystemNotCached => write!(f, "SystemNotCached"),576Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(),577Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),578Self::Skipped(arg0) => f.debug_tuple("Skipped").field(arg0).finish(),579Self::Failed(arg0) => f.debug_tuple("Failed").field(arg0).finish(),580Self::IncorrectType(arg0, arg1) => f581.debug_tuple("IncorrectType")582.field(arg0)583.field(arg1)584.finish(),585}586}587}588589#[cfg(test)]590mod tests {591use core::cell::Cell;592593use bevy_utils::default;594595use crate::{596prelude::*,597system::{RegisteredSystemError, SystemId},598};599600#[derive(Resource, Default, PartialEq, Debug)]601struct Counter(u8);602603#[test]604fn change_detection() {605#[derive(Resource, Default)]606struct ChangeDetector;607608fn count_up_iff_changed(609mut counter: ResMut<Counter>,610change_detector: ResMut<ChangeDetector>,611) {612if change_detector.is_changed() {613counter.0 += 1;614}615}616617let mut world = World::new();618world.init_resource::<ChangeDetector>();619world.init_resource::<Counter>();620assert_eq!(*world.resource::<Counter>(), Counter(0));621// Resources are changed when they are first added.622let id = world.register_system(count_up_iff_changed);623world.run_system(id).expect("system runs successfully");624assert_eq!(*world.resource::<Counter>(), Counter(1));625// Nothing changed626world.run_system(id).expect("system runs successfully");627assert_eq!(*world.resource::<Counter>(), Counter(1));628// Making a change629world.resource_mut::<ChangeDetector>().set_changed();630world.run_system(id).expect("system runs successfully");631assert_eq!(*world.resource::<Counter>(), Counter(2));632}633634#[test]635fn local_variables() {636// The `Local` begins at the default value of 0637fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {638counter.0 += last_counter.0 .0;639last_counter.0 .0 = counter.0;640}641642let mut world = World::new();643world.insert_resource(Counter(1));644assert_eq!(*world.resource::<Counter>(), Counter(1));645let id = world.register_system(doubling);646world.run_system(id).expect("system runs successfully");647assert_eq!(*world.resource::<Counter>(), Counter(1));648world.run_system(id).expect("system runs successfully");649assert_eq!(*world.resource::<Counter>(), Counter(2));650world.run_system(id).expect("system runs successfully");651assert_eq!(*world.resource::<Counter>(), Counter(4));652world.run_system(id).expect("system runs successfully");653assert_eq!(*world.resource::<Counter>(), Counter(8));654}655656#[test]657fn input_values() {658// Verify that a non-Copy, non-Clone type can be passed in.659struct NonCopy(u8);660661fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {662counter.0 += increment_by;663}664665let mut world = World::new();666667let id = world.register_system(increment_sys);668669// Insert the resource after registering the system.670world.insert_resource(Counter(1));671assert_eq!(*world.resource::<Counter>(), Counter(1));672673world674.run_system_with(id, NonCopy(1))675.expect("system runs successfully");676assert_eq!(*world.resource::<Counter>(), Counter(2));677678world679.run_system_with(id, NonCopy(1))680.expect("system runs successfully");681assert_eq!(*world.resource::<Counter>(), Counter(3));682683world684.run_system_with(id, NonCopy(20))685.expect("system runs successfully");686assert_eq!(*world.resource::<Counter>(), Counter(23));687688world689.run_system_with(id, NonCopy(1))690.expect("system runs successfully");691assert_eq!(*world.resource::<Counter>(), Counter(24));692}693694#[test]695fn output_values() {696// Verify that a non-Copy, non-Clone type can be returned.697#[derive(Eq, PartialEq, Debug)]698struct NonCopy(u8);699700fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {701counter.0 += 1;702NonCopy(counter.0)703}704705let mut world = World::new();706707let id = world.register_system(increment_sys);708709// Insert the resource after registering the system.710world.insert_resource(Counter(1));711assert_eq!(*world.resource::<Counter>(), Counter(1));712713let output = world.run_system(id).expect("system runs successfully");714assert_eq!(*world.resource::<Counter>(), Counter(2));715assert_eq!(output, NonCopy(2));716717let output = world.run_system(id).expect("system runs successfully");718assert_eq!(*world.resource::<Counter>(), Counter(3));719assert_eq!(output, NonCopy(3));720}721722#[test]723fn fallible_system() {724fn sys() -> Result<()> {725Err("error")?;726Ok(())727}728729let mut world = World::new();730let fallible_system_id = world.register_system(sys);731let output = world.run_system(fallible_system_id);732assert!(matches!(output, Ok(Err(_))));733}734735#[test]736fn exclusive_system() {737let mut world = World::new();738let exclusive_system_id = world.register_system(|world: &mut World| {739world.spawn_empty();740});741let entity_count = world.entities.len();742let _ = world.run_system(exclusive_system_id);743assert_eq!(world.entities.len(), entity_count + 1);744}745746#[test]747fn nested_systems() {748use crate::system::SystemId;749750#[derive(Component)]751struct Callback(SystemId);752753fn nested(query: Query<&Callback>, mut commands: Commands) {754for callback in query.iter() {755commands.run_system(callback.0);756}757}758759let mut world = World::new();760world.insert_resource(Counter(0));761762let increment_two = world.register_system(|mut counter: ResMut<Counter>| {763counter.0 += 2;764});765let increment_three = world.register_system(|mut counter: ResMut<Counter>| {766counter.0 += 3;767});768let nested_id = world.register_system(nested);769770world.spawn(Callback(increment_two));771world.spawn(Callback(increment_three));772let _ = world.run_system(nested_id);773assert_eq!(*world.resource::<Counter>(), Counter(5));774}775776#[test]777fn nested_systems_with_inputs() {778use crate::system::SystemId;779780#[derive(Component)]781struct Callback(SystemId<In<u8>>, u8);782783fn nested(query: Query<&Callback>, mut commands: Commands) {784for callback in query.iter() {785commands.run_system_with(callback.0, callback.1);786}787}788789let mut world = World::new();790world.insert_resource(Counter(0));791792let increment_by =793world.register_system(|In(amt): In<u8>, mut counter: ResMut<Counter>| {794counter.0 += amt;795});796let nested_id = world.register_system(nested);797798world.spawn(Callback(increment_by, 2));799world.spawn(Callback(increment_by, 3));800let _ = world.run_system(nested_id);801assert_eq!(*world.resource::<Counter>(), Counter(5));802}803804#[test]805fn cached_system() {806use crate::system::RegisteredSystemError;807808fn four() -> i32 {8094810}811812let mut world = World::new();813let old = world.register_system_cached(four);814let new = world.register_system_cached(four);815assert_eq!(old, new);816817let result = world.unregister_system_cached(four);818assert!(result.is_ok());819let new = world.register_system_cached(four);820assert_ne!(old, new);821822let output = world.run_system(old);823assert!(matches!(824output,825Err(RegisteredSystemError::SystemIdNotRegistered(x)) if x == old,826));827let output = world.run_system(new);828assert!(matches!(output, Ok(x) if x == four()));829let output = world.run_system_cached(four);830assert!(matches!(output, Ok(x) if x == four()));831let output = world.run_system_cached_with(four, ());832assert!(matches!(output, Ok(x) if x == four()));833}834835#[test]836fn cached_fallible_system() {837fn sys() -> Result<()> {838Err("error")?;839Ok(())840}841842let mut world = World::new();843let fallible_system_id = world.register_system_cached(sys);844let output = world.run_system(fallible_system_id);845assert!(matches!(output, Ok(Err(_))));846let output = world.run_system_cached(sys);847assert!(matches!(output, Ok(Err(_))));848let output = world.run_system_cached_with(sys, ());849assert!(matches!(output, Ok(Err(_))));850}851852#[test]853fn cached_system_commands() {854fn sys(mut counter: ResMut<Counter>) {855counter.0 += 1;856}857858let mut world = World::new();859world.insert_resource(Counter(0));860world.commands().run_system_cached(sys);861world.flush_commands();862assert_eq!(world.resource::<Counter>().0, 1);863world.commands().run_system_cached_with(sys, ());864world.flush_commands();865assert_eq!(world.resource::<Counter>().0, 2);866}867868#[test]869fn cached_fallible_system_commands() {870fn sys(mut counter: ResMut<Counter>) -> Result {871counter.0 += 1;872Ok(())873}874875let mut world = World::new();876world.insert_resource(Counter(0));877world.commands().run_system_cached(sys);878world.flush_commands();879assert_eq!(world.resource::<Counter>().0, 1);880world.commands().run_system_cached_with(sys, ());881world.flush_commands();882assert_eq!(world.resource::<Counter>().0, 2);883}884885#[test]886#[should_panic(expected = "This system always fails")]887fn cached_fallible_system_commands_can_fail() {888use crate::system::command;889fn sys() -> Result {890Err("This system always fails".into())891}892893let mut world = World::new();894world.commands().queue(command::run_system_cached(sys));895world.flush_commands();896}897898#[test]899fn cached_system_adapters() {900fn four() -> i32 {9014902}903904fn double(In(i): In<i32>) -> i32 {905i * 2906}907908let mut world = World::new();909910let output = world.run_system_cached(four.pipe(double));911assert!(matches!(output, Ok(8)));912913let output = world.run_system_cached(four.map(|i| i * 2));914assert!(matches!(output, Ok(8)));915}916917#[test]918fn cached_system_into_same_system_type() {919struct Foo;920impl IntoSystem<(), (), ()> for Foo {921type System = ApplyDeferred;922fn into_system(_: Self) -> Self::System {923ApplyDeferred924}925}926927struct Bar;928impl IntoSystem<(), (), ()> for Bar {929type System = ApplyDeferred;930fn into_system(_: Self) -> Self::System {931ApplyDeferred932}933}934935let mut world = World::new();936let foo1 = world.register_system_cached(Foo);937let foo2 = world.register_system_cached(Foo);938let bar1 = world.register_system_cached(Bar);939let bar2 = world.register_system_cached(Bar);940941// The `S: IntoSystem` types are different, so they should be cached942// as separate systems, even though the `<S as IntoSystem>::System`943// types / values are the same (`ApplyDeferred`).944assert_ne!(foo1, bar1);945946// But if the `S: IntoSystem` types are the same, they'll be cached947// as the same system.948assert_eq!(foo1, foo2);949assert_eq!(bar1, bar2);950}951952#[test]953fn system_with_input_ref() {954fn with_ref(InRef(input): InRef<u8>, mut counter: ResMut<Counter>) {955counter.0 += *input;956}957958let mut world = World::new();959world.insert_resource(Counter(0));960961let id = world.register_system(with_ref);962world.run_system_with(id, &2).unwrap();963assert_eq!(*world.resource::<Counter>(), Counter(2));964}965966#[test]967fn system_with_input_mut() {968#[derive(Event)]969struct MyEvent {970cancelled: bool,971}972973fn post(InMut(event): InMut<MyEvent>, counter: ResMut<Counter>) {974if counter.0 > 0 {975event.cancelled = true;976}977}978979let mut world = World::new();980world.insert_resource(Counter(0));981let post_system = world.register_system(post);982983let mut event = MyEvent { cancelled: false };984world.run_system_with(post_system, &mut event).unwrap();985assert!(!event.cancelled);986987world.resource_mut::<Counter>().0 = 1;988world.run_system_with(post_system, &mut event).unwrap();989assert!(event.cancelled);990}991992#[test]993fn run_system_invalid_params() {994use crate::system::RegisteredSystemError;995use alloc::string::ToString;996997struct T;998impl Resource for T {}999fn system(_: Res<T>) {}10001001let mut world = World::new();1002let id = world.register_system(system);1003// This fails because `T` has not been added to the world yet.1004let result = world.run_system(id);10051006assert!(matches!(result, Err(RegisteredSystemError::Failed { .. })));1007let expected = "Resource does not exist";1008let actual = result.unwrap_err().to_string();10091010assert!(1011actual.contains(expected),1012"Expected error message to contain `{}` but got `{}`",1013expected,1014actual1015);1016}10171018#[test]1019fn run_system_recursive() {1020std::thread_local! {1021static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };1022static SYSTEM_ID: Cell<Option<SystemId>> = default();1023}10241025fn system(mut commands: Commands) {1026let count = INVOCATIONS_LEFT.get() - 1;1027INVOCATIONS_LEFT.set(count);1028if count > 0 {1029commands.run_system(SYSTEM_ID.get().unwrap());1030}1031}10321033let mut world = World::new();1034let id = world.register_system(system);1035SYSTEM_ID.set(Some(id));1036world.run_system(id).unwrap();10371038assert_eq!(INVOCATIONS_LEFT.get(), 0);1039}10401041#[test]1042fn run_system_exclusive_adapters() {1043let mut world = World::new();1044fn system(_: &mut World) {}1045world.run_system_cached(system).unwrap();1046world.run_system_cached(system.pipe(system)).unwrap();1047world.run_system_cached(system.map(|()| {})).unwrap();1048}10491050#[test]1051fn wrong_system_type() {1052fn test() -> Result<(), u8> {1053Ok(())1054}10551056let mut world = World::new();10571058let entity = world.register_system_cached(test).entity();10591060match world.run_system::<u8>(SystemId::from_entity(entity)) {1061Ok(_) => panic!("Should fail since called `run_system` with wrong SystemId type."),1062Err(RegisteredSystemError::IncorrectType(_, _)) => (),1063Err(err) => panic!("Failed with wrong error. `{:?}`", err),1064}1065}1066}106710681069