Path: blob/main/crates/bevy_ecs/src/system/system_registry.rs
9418 views
#[cfg(feature = "hotpatching")]1use crate::{change_detection::DetectChanges, HotPatchChanges};2use crate::{3change_detection::Mut,4entity::Entity,5error::BevyError,6system::{7input::SystemInput, BoxedSystem, IntoSystem, RunSystemError, SystemParamValidationError,8},9world::World,10};11use alloc::boxed::Box;12use bevy_ecs_macros::{Component, Resource};13use bevy_utils::prelude::DebugName;14use core::{any::TypeId, marker::PhantomData};15use thiserror::Error;1617/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.18#[derive(Component)]19#[require(SystemIdMarker = SystemIdMarker::typed_system_id_marker::<I, O>())]20pub(crate) struct RegisteredSystem<I, O> {21initialized: bool,22system: Option<BoxedSystem<I, O>>,23}2425impl<I, O> RegisteredSystem<I, O> {26pub fn new(system: BoxedSystem<I, O>) -> Self {27RegisteredSystem {28initialized: false,29system: Some(system),30}31}32}3334#[derive(Debug, Clone)]35struct TypeIdAndName {36type_id: TypeId,37name: DebugName,38}3940impl TypeIdAndName {41fn new<T: 'static>() -> Self {42Self {43type_id: TypeId::of::<T>(),44name: DebugName::type_name::<T>(),45}46}47}4849impl Default for TypeIdAndName {50fn default() -> Self {51Self {52type_id: TypeId::of::<()>(),53name: DebugName::type_name::<()>(),54}55}56}5758/// Marker [`Component`](bevy_ecs::component::Component) for identifying [`SystemId`] [`Entity`]s.59#[derive(Debug, Default, Clone, Component)]60pub struct SystemIdMarker {61input_type_id: TypeIdAndName,62output_type_id: TypeIdAndName,63}6465impl SystemIdMarker {66fn typed_system_id_marker<I: 'static, O: 'static>() -> Self {67Self {68input_type_id: TypeIdAndName::new::<I>(),69output_type_id: TypeIdAndName::new::<O>(),70}71}72}7374/// A system that has been removed from the registry.75/// It contains the system and whether or not it has been initialized.76///77/// This struct is returned by [`World::unregister_system`].78pub struct RemovedSystem<I = (), O = ()> {79initialized: bool,80system: BoxedSystem<I, O>,81}8283impl<I, O> RemovedSystem<I, O> {84/// Is the system initialized?85/// A system is initialized the first time it's ran.86pub fn initialized(&self) -> bool {87self.initialized88}8990/// The system removed from the storage.91pub fn system(self) -> BoxedSystem<I, O> {92self.system93}94}9596/// An identifier for a registered system.97///98/// These are opaque identifiers, keyed to a specific [`World`],99/// and are created via [`World::register_system`].100pub struct SystemId<I: SystemInput = (), O = ()> {101pub(crate) entity: Entity,102pub(crate) marker: PhantomData<fn(I) -> O>,103}104105impl<I: SystemInput, O> SystemId<I, O> {106/// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state.107///108/// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system109/// is really an entity with associated handler function.110///111/// For example, this is useful if you want to assign a name label to a system.112pub fn entity(self) -> Entity {113self.entity114}115116/// Create [`SystemId`] from an [`Entity`]. Useful when you only have entity handles to avoid117/// adding extra components that have a [`SystemId`] everywhere. To run a system with this ID118/// - The entity must be a system119/// - The `I` + `O` types must be correct120pub fn from_entity(entity: Entity) -> Self {121Self {122entity,123marker: PhantomData,124}125}126}127128impl<I: SystemInput, O> Eq for SystemId<I, O> {}129130// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.131impl<I: SystemInput, O> Copy for SystemId<I, O> {}132133// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.134impl<I: SystemInput, O> Clone for SystemId<I, O> {135fn clone(&self) -> Self {136*self137}138}139140// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.141impl<I: SystemInput, O> PartialEq for SystemId<I, O> {142fn eq(&self, other: &Self) -> bool {143self.entity == other.entity && self.marker == other.marker144}145}146147// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.148impl<I: SystemInput, O> core::hash::Hash for SystemId<I, O> {149fn hash<H: core::hash::Hasher>(&self, state: &mut H) {150self.entity.hash(state);151}152}153154impl<I: SystemInput, O> core::fmt::Debug for SystemId<I, O> {155fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {156f.debug_tuple("SystemId").field(&self.entity).finish()157}158}159160/// A cached [`SystemId`] distinguished by the unique function type of its system.161///162/// This resource is inserted by [`World::register_system_cached`].163#[derive(Resource)]164pub struct CachedSystemId<S> {165/// The cached `SystemId` as an `Entity`.166pub entity: Entity,167_marker: PhantomData<fn() -> S>,168}169170impl<S> CachedSystemId<S> {171/// Creates a new `CachedSystemId` struct given a `SystemId`.172pub fn new<I: SystemInput, O>(id: SystemId<I, O>) -> Self {173Self {174entity: id.entity(),175_marker: PhantomData,176}177}178}179180impl World {181/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].182///183/// It's possible to register multiple copies of the same system by calling this function184/// multiple times. If that's not what you want, consider using [`World::register_system_cached`]185/// instead.186///187/// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),188/// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.189/// This allows for running systems in a pushed-based fashion.190/// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases191/// due to its better performance and ability to run non-conflicting systems simultaneously.192pub fn register_system<I, O, M>(193&mut self,194system: impl IntoSystem<I, O, M> + 'static,195) -> SystemId<I, O>196where197I: SystemInput + 'static,198O: 'static,199{200self.register_boxed_system(Box::new(IntoSystem::into_system(system)))201}202203/// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].204///205/// This is useful if the [`IntoSystem`] implementor has already been turned into a206/// [`System`](crate::system::System) trait object and put in a [`Box`].207pub fn register_boxed_system<I, O>(&mut self, system: BoxedSystem<I, O>) -> SystemId<I, O>208where209I: SystemInput + 'static,210O: 'static,211{212let entity = self.spawn(RegisteredSystem::new(system)).id();213SystemId::from_entity(entity)214}215216/// Removes a registered system and returns the system, if it exists.217/// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors.218/// Re-adding the removed system will register it on a new [`SystemId`].219///220/// If no system corresponds to the given [`SystemId`], this method returns an error.221/// Systems are also not allowed to remove themselves, this returns an error too.222pub fn unregister_system<I, O>(223&mut self,224id: SystemId<I, O>,225) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>226where227I: SystemInput + 'static,228O: 'static,229{230match self.get_entity_mut(id.entity) {231Ok(mut entity) => {232let registered_system = entity233.take::<RegisteredSystem<I, O>>()234.ok_or(RegisteredSystemError::SelfRemove(id))?;235entity.despawn();236Ok(RemovedSystem {237initialized: registered_system.initialized,238system: registered_system239.system240.ok_or(RegisteredSystemError::SystemMissing(id))?,241})242}243Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)),244}245}246247/// Run stored systems by their [`SystemId`].248/// Before running a system, it must first be registered.249/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].250/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),251/// because it keeps local state between calls and change detection works correctly.252///253/// Also runs any queued-up commands.254///255/// In order to run a chained system with an input, use [`World::run_system_with`] instead.256///257/// # Examples258///259/// ## Running a system260///261/// ```262/// # use bevy_ecs::prelude::*;263/// fn increment(mut counter: Local<u8>) {264/// *counter += 1;265/// println!("{}", *counter);266/// }267///268/// let mut world = World::default();269/// let counter_one = world.register_system(increment);270/// let counter_two = world.register_system(increment);271/// world.run_system(counter_one); // -> 1272/// world.run_system(counter_one); // -> 2273/// world.run_system(counter_two); // -> 1274/// ```275///276/// ## Change detection277///278/// ```279/// # use bevy_ecs::prelude::*;280/// #[derive(Resource, Default)]281/// struct ChangeDetector;282///283/// let mut world = World::default();284/// world.init_resource::<ChangeDetector>();285/// let detector = world.register_system(|change_detector: ResMut<ChangeDetector>| {286/// if change_detector.is_changed() {287/// println!("Something happened!");288/// } else {289/// println!("Nothing happened.");290/// }291/// });292///293/// // Resources are changed when they are first added294/// let _ = world.run_system(detector); // -> Something happened!295/// let _ = world.run_system(detector); // -> Nothing happened.296/// world.resource_mut::<ChangeDetector>().set_changed();297/// let _ = world.run_system(detector); // -> Something happened!298/// ```299///300/// ## Getting system output301///302/// ```303/// # use bevy_ecs::prelude::*;304///305/// #[derive(Resource)]306/// struct PlayerScore(i32);307///308/// #[derive(Resource)]309/// struct OpponentScore(i32);310///311/// fn get_player_score(player_score: Res<PlayerScore>) -> i32 {312/// player_score.0313/// }314///315/// fn get_opponent_score(opponent_score: Res<OpponentScore>) -> i32 {316/// opponent_score.0317/// }318///319/// let mut world = World::default();320/// world.insert_resource(PlayerScore(3));321/// world.insert_resource(OpponentScore(2));322///323/// let scoring_systems = [324/// ("player", world.register_system(get_player_score)),325/// ("opponent", world.register_system(get_opponent_score)),326/// ];327///328/// for (label, scoring_system) in scoring_systems {329/// println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded"));330/// }331/// ```332pub fn run_system<O: 'static>(333&mut self,334id: SystemId<(), O>,335) -> Result<O, RegisteredSystemError<(), O>> {336self.run_system_with(id, ())337}338339/// Run a stored chained system by its [`SystemId`], providing an input value.340/// Before running a system, it must first be registered.341/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].342///343/// Also runs any queued-up commands.344///345/// # Examples346///347/// ```348/// # use bevy_ecs::prelude::*;349/// fn increment(In(increment_by): In<u8>, mut counter: Local<u8>) -> u8 {350/// *counter += increment_by;351/// *counter352/// }353///354/// let mut world = World::default();355/// let counter_one = world.register_system(increment);356/// let counter_two = world.register_system(increment);357/// assert_eq!(world.run_system_with(counter_one, 1).unwrap(), 1);358/// assert_eq!(world.run_system_with(counter_one, 20).unwrap(), 21);359/// assert_eq!(world.run_system_with(counter_two, 30).unwrap(), 30);360/// ```361///362/// See [`World::run_system`] for more examples.363pub fn run_system_with<I, O>(364&mut self,365id: SystemId<I, O>,366input: I::Inner<'_>,367) -> Result<O, RegisteredSystemError<I, O>>368where369I: SystemInput + 'static,370O: 'static,371{372// Lookup373let mut entity = self374.get_entity_mut(id.entity)375.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;376377// Take ownership of system trait object378let Some(mut registered_system) = entity.get_mut::<RegisteredSystem<I, O>>() else {379let Some(system_id_marker) = entity.get::<SystemIdMarker>() else {380return Err(RegisteredSystemError::SystemIdNotRegistered(id));381};382if system_id_marker.input_type_id.type_id != TypeId::of::<I>()383|| system_id_marker.output_type_id.type_id != TypeId::of::<O>()384{385return Err(RegisteredSystemError::IncorrectType(386id,387system_id_marker.clone(),388));389}390return Err(RegisteredSystemError::MissingRegisteredSystemComponent(id));391};392393let mut system = registered_system394.system395.take()396.ok_or(RegisteredSystemError::SystemMissing(id))?;397398// Initialize the system399if !registered_system.initialized {400system.initialize(self);401}402403// refresh hotpatches for stored systems404#[cfg(feature = "hotpatching")]405if self406.get_resource_ref::<HotPatchChanges>()407.map(|r| r.last_changed())408.unwrap_or_default()409.is_newer_than(system.get_last_run(), self.change_tick())410{411system.refresh_hotpatch();412}413414// Wait to run the commands until the system is available again.415// This is needed so the systems can recursively run themselves.416let result = system.run_without_applying_deferred(input, self);417system.queue_deferred(self.into());418419// Return ownership of system trait object (if entity still exists)420if let Ok(mut entity) = self.get_entity_mut(id.entity)421&& let Some(mut registered_system) = entity.get_mut::<RegisteredSystem<I, O>>()422{423registered_system.system = Some(system);424registered_system.initialized = true;425}426427// Run any commands enqueued by the system428self.flush();429Ok(result?)430}431432/// Registers a system or returns its cached [`SystemId`].433///434/// If you want to run the system immediately and you don't need its `SystemId`, see435/// [`World::run_system_cached`].436///437/// The first time this function is called for a particular system, it will register it and438/// store its [`SystemId`] in a [`CachedSystemId`] resource for later. If you would rather439/// manage the `SystemId` yourself, or register multiple copies of the same system, use440/// [`World::register_system`] instead.441///442/// # Limitations443///444/// This function only accepts ZST (zero-sized) systems to guarantee that any two systems of445/// the same type must be equal. This means that closures that capture the environment, and446/// function pointers, are not accepted.447///448/// If you want to access values from the environment within a system, consider passing them in449/// as inputs via [`World::run_system_cached_with`]. If that's not an option, consider450/// [`World::register_system`] instead.451pub fn register_system_cached<I, O, M, S>(&mut self, system: S) -> SystemId<I, O>452where453I: SystemInput + 'static,454O: 'static,455S: IntoSystem<I, O, M> + 'static,456{457const {458assert!(459size_of::<S>() == 0,460"Non-ZST systems (e.g. capturing closures, function pointers) cannot be cached.",461);462}463464if !self.contains_resource::<CachedSystemId<S>>() {465let id = self.register_system(system);466self.insert_resource(CachedSystemId::<S>::new(id));467return id;468}469470self.resource_scope(|world, mut id: Mut<CachedSystemId<S>>| {471if let Ok(mut entity) = world.get_entity_mut(id.entity) {472if !entity.contains::<RegisteredSystem<I, O>>() {473entity.insert(RegisteredSystem::new(Box::new(IntoSystem::into_system(474system,475))));476}477} else {478id.entity = world.register_system(system).entity();479}480SystemId::from_entity(id.entity)481})482}483484/// Removes a cached system and its [`CachedSystemId`] resource.485///486/// See [`World::register_system_cached`] for more information.487pub fn unregister_system_cached<I, O, M, S>(488&mut self,489_system: S,490) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>491where492I: SystemInput + 'static,493O: 'static,494S: IntoSystem<I, O, M> + 'static,495{496let id = self497.remove_resource::<CachedSystemId<S>>()498.ok_or(RegisteredSystemError::SystemNotCached)?;499self.unregister_system(SystemId::<I, O>::from_entity(id.entity))500}501502/// Runs a cached system, registering it if necessary.503///504/// See [`World::register_system_cached`] for more information.505pub fn run_system_cached<O: 'static, M, S: IntoSystem<(), O, M> + 'static>(506&mut self,507system: S,508) -> Result<O, RegisteredSystemError<(), O>> {509self.run_system_cached_with(system, ())510}511512/// Runs a cached system with an input, registering it if necessary.513///514/// See [`World::register_system_cached`] for more information.515pub fn run_system_cached_with<I, O, M, S>(516&mut self,517system: S,518input: I::Inner<'_>,519) -> Result<O, RegisteredSystemError<I, O>>520where521I: SystemInput + 'static,522O: 'static,523S: IntoSystem<I, O, M> + 'static,524{525let id = self.register_system_cached(system);526self.run_system_with(id, input)527}528}529530/// An operation with stored systems failed.531#[derive(Error)]532pub enum RegisteredSystemError<I: SystemInput = (), O = ()> {533/// A system was run by id, but no system with that id was found.534///535/// Did you forget to register it?536#[error("System {0:?} was not registered")]537SystemIdNotRegistered(SystemId<I, O>),538/// A cached system was removed by value, but no system with its type was found.539///540/// Did you forget to register it?541#[error("Cached system was not found")]542SystemNotCached,543/// The `RegisteredSystem` component is missing.544#[error("System {0:?} does not have a RegisteredSystem component. This only happens if app code removed the component.")]545MissingRegisteredSystemComponent(SystemId<I, O>),546/// A system tried to remove itself.547#[error("System {0:?} tried to remove itself")]548SelfRemove(SystemId<I, O>),549/// System could not be run due to parameters that failed validation.550/// This is not considered an error.551#[error("System did not run due to failed parameter validation: {0}")]552Skipped(SystemParamValidationError),553/// System returned an error or failed required parameter validation.554#[error("System returned error: {0}")]555Failed(BevyError),556/// [`SystemId`] had different input and/or output types than [`SystemIdMarker`]557#[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)]558IncorrectType(SystemId<I, O>, SystemIdMarker),559/// System is not present in the `RegisteredSystem` component.560// TODO: We should consider using catch_unwind to protect against the panic case.561#[error("The system is not present in the RegisteredSystem component. This can happen if the system was called recursively or if the system panicked on the last run.")]562SystemMissing(SystemId<I, O>),563}564565impl<I: SystemInput, O> From<RunSystemError> for RegisteredSystemError<I, O> {566fn from(value: RunSystemError) -> Self {567match value {568RunSystemError::Skipped(err) => Self::Skipped(err),569RunSystemError::Failed(err) => Self::Failed(err),570}571}572}573574impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {575fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {576match self {577Self::SystemIdNotRegistered(arg0) => {578f.debug_tuple("SystemIdNotRegistered").field(arg0).finish()579}580Self::SystemNotCached => write!(f, "SystemNotCached"),581Self::MissingRegisteredSystemComponent(arg0) => f582.debug_tuple("MissingRegisteredSystemComponent")583.field(arg0)584.finish(),585Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),586Self::Skipped(arg0) => f.debug_tuple("Skipped").field(arg0).finish(),587Self::Failed(arg0) => f.debug_tuple("Failed").field(arg0).finish(),588Self::IncorrectType(arg0, arg1) => f589.debug_tuple("IncorrectType")590.field(arg0)591.field(arg1)592.finish(),593Self::SystemMissing(arg0) => f.debug_tuple("SystemMissing").field(arg0).finish(),594}595}596}597598#[cfg(test)]599mod tests {600use core::cell::Cell;601602use bevy_utils::default;603604use crate::{605prelude::*,606system::{RegisteredSystemError, SystemId},607};608609#[derive(Resource, Default, PartialEq, Debug)]610struct Counter(u8);611612#[test]613fn change_detection() {614#[derive(Resource, Default)]615struct ChangeDetector;616617fn count_up_iff_changed(618mut counter: ResMut<Counter>,619change_detector: ResMut<ChangeDetector>,620) {621if change_detector.is_changed() {622counter.0 += 1;623}624}625626let mut world = World::new();627world.init_resource::<ChangeDetector>();628world.init_resource::<Counter>();629assert_eq!(*world.resource::<Counter>(), Counter(0));630// Resources are changed when they are first added.631let id = world.register_system(count_up_iff_changed);632world.run_system(id).expect("system runs successfully");633assert_eq!(*world.resource::<Counter>(), Counter(1));634// Nothing changed635world.run_system(id).expect("system runs successfully");636assert_eq!(*world.resource::<Counter>(), Counter(1));637// Making a change638world.resource_mut::<ChangeDetector>().set_changed();639world.run_system(id).expect("system runs successfully");640assert_eq!(*world.resource::<Counter>(), Counter(2));641}642643#[test]644fn local_variables() {645// The `Local` begins at the default value of 0646fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {647counter.0 += last_counter.0 .0;648last_counter.0 .0 = counter.0;649}650651let mut world = World::new();652world.insert_resource(Counter(1));653assert_eq!(*world.resource::<Counter>(), Counter(1));654let id = world.register_system(doubling);655world.run_system(id).expect("system runs successfully");656assert_eq!(*world.resource::<Counter>(), Counter(1));657world.run_system(id).expect("system runs successfully");658assert_eq!(*world.resource::<Counter>(), Counter(2));659world.run_system(id).expect("system runs successfully");660assert_eq!(*world.resource::<Counter>(), Counter(4));661world.run_system(id).expect("system runs successfully");662assert_eq!(*world.resource::<Counter>(), Counter(8));663}664665#[test]666fn input_values() {667// Verify that a non-Copy, non-Clone type can be passed in.668struct NonCopy(u8);669670fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {671counter.0 += increment_by;672}673674let mut world = World::new();675676let id = world.register_system(increment_sys);677678// Insert the resource after registering the system.679world.insert_resource(Counter(1));680assert_eq!(*world.resource::<Counter>(), Counter(1));681682world683.run_system_with(id, NonCopy(1))684.expect("system runs successfully");685assert_eq!(*world.resource::<Counter>(), Counter(2));686687world688.run_system_with(id, NonCopy(1))689.expect("system runs successfully");690assert_eq!(*world.resource::<Counter>(), Counter(3));691692world693.run_system_with(id, NonCopy(20))694.expect("system runs successfully");695assert_eq!(*world.resource::<Counter>(), Counter(23));696697world698.run_system_with(id, NonCopy(1))699.expect("system runs successfully");700assert_eq!(*world.resource::<Counter>(), Counter(24));701}702703#[test]704fn output_values() {705// Verify that a non-Copy, non-Clone type can be returned.706#[derive(Eq, PartialEq, Debug)]707struct NonCopy(u8);708709fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {710counter.0 += 1;711NonCopy(counter.0)712}713714let mut world = World::new();715716let id = world.register_system(increment_sys);717718// Insert the resource after registering the system.719world.insert_resource(Counter(1));720assert_eq!(*world.resource::<Counter>(), Counter(1));721722let output = world.run_system(id).expect("system runs successfully");723assert_eq!(*world.resource::<Counter>(), Counter(2));724assert_eq!(output, NonCopy(2));725726let output = world.run_system(id).expect("system runs successfully");727assert_eq!(*world.resource::<Counter>(), Counter(3));728assert_eq!(output, NonCopy(3));729}730731#[test]732fn fallible_system() {733fn sys() -> Result<()> {734Err("error")?;735Ok(())736}737738let mut world = World::new();739let fallible_system_id = world.register_system(sys);740let output = world.run_system(fallible_system_id);741assert!(matches!(output, Ok(Err(_))));742}743744#[test]745fn exclusive_system() {746let mut world = World::new();747let exclusive_system_id = world.register_system(|world: &mut World| {748world.spawn_empty();749});750let entity_count = world.entities.count_spawned();751let _ = world.run_system(exclusive_system_id);752assert_eq!(world.entities.count_spawned(), entity_count + 1);753}754755#[test]756fn nested_systems() {757use crate::system::SystemId;758759#[derive(Component)]760struct Callback(SystemId);761762fn nested(query: Query<&Callback>, mut commands: Commands) {763for callback in query.iter() {764commands.run_system(callback.0);765}766}767768let mut world = World::new();769world.insert_resource(Counter(0));770771let increment_two = world.register_system(|mut counter: ResMut<Counter>| {772counter.0 += 2;773});774let increment_three = world.register_system(|mut counter: ResMut<Counter>| {775counter.0 += 3;776});777let nested_id = world.register_system(nested);778779world.spawn(Callback(increment_two));780world.spawn(Callback(increment_three));781let _ = world.run_system(nested_id);782assert_eq!(*world.resource::<Counter>(), Counter(5));783}784785#[test]786fn nested_systems_with_inputs() {787use crate::system::SystemId;788789#[derive(Component)]790struct Callback(SystemId<In<u8>>, u8);791792fn nested(query: Query<&Callback>, mut commands: Commands) {793for callback in query.iter() {794commands.run_system_with(callback.0, callback.1);795}796}797798let mut world = World::new();799world.insert_resource(Counter(0));800801let increment_by =802world.register_system(|In(amt): In<u8>, mut counter: ResMut<Counter>| {803counter.0 += amt;804});805let nested_id = world.register_system(nested);806807world.spawn(Callback(increment_by, 2));808world.spawn(Callback(increment_by, 3));809let _ = world.run_system(nested_id);810assert_eq!(*world.resource::<Counter>(), Counter(5));811}812813#[test]814fn cached_system() {815use crate::system::RegisteredSystemError;816817fn four() -> i32 {8184819}820821let mut world = World::new();822let old = world.register_system_cached(four);823let new = world.register_system_cached(four);824assert_eq!(old, new);825826let result = world.unregister_system_cached(four);827assert!(result.is_ok());828let new = world.register_system_cached(four);829assert_ne!(old, new);830831let output = world.run_system(old);832assert!(matches!(833output,834Err(RegisteredSystemError::SystemIdNotRegistered(x)) if x == old,835));836let output = world.run_system(new);837assert!(matches!(output, Ok(x) if x == four()));838let output = world.run_system_cached(four);839assert!(matches!(output, Ok(x) if x == four()));840let output = world.run_system_cached_with(four, ());841assert!(matches!(output, Ok(x) if x == four()));842}843844#[test]845fn cached_fallible_system() {846fn sys() -> Result<()> {847Err("error")?;848Ok(())849}850851let mut world = World::new();852let fallible_system_id = world.register_system_cached(sys);853let output = world.run_system(fallible_system_id);854assert!(matches!(output, Ok(Err(_))));855let output = world.run_system_cached(sys);856assert!(matches!(output, Ok(Err(_))));857let output = world.run_system_cached_with(sys, ());858assert!(matches!(output, Ok(Err(_))));859}860861#[test]862fn cached_system_commands() {863fn sys(mut counter: ResMut<Counter>) {864counter.0 += 1;865}866867let mut world = World::new();868world.insert_resource(Counter(0));869world.commands().run_system_cached(sys);870world.flush_commands();871assert_eq!(world.resource::<Counter>().0, 1);872world.commands().run_system_cached_with(sys, ());873world.flush_commands();874assert_eq!(world.resource::<Counter>().0, 2);875}876877#[test]878fn cached_fallible_system_commands() {879fn sys(mut counter: ResMut<Counter>) -> Result {880counter.0 += 1;881Ok(())882}883884let mut world = World::new();885world.insert_resource(Counter(0));886world.commands().run_system_cached(sys);887world.flush_commands();888assert_eq!(world.resource::<Counter>().0, 1);889world.commands().run_system_cached_with(sys, ());890world.flush_commands();891assert_eq!(world.resource::<Counter>().0, 2);892}893894#[test]895#[should_panic(expected = "This system always fails")]896fn cached_fallible_system_commands_can_fail() {897use crate::system::command;898fn sys() -> Result {899Err("This system always fails".into())900}901902let mut world = World::new();903world.commands().queue(command::run_system_cached(sys));904world.flush_commands();905}906907#[test]908fn cached_system_adapters() {909fn four() -> i32 {9104911}912913fn double(In(i): In<i32>) -> i32 {914i * 2915}916917let mut world = World::new();918919let output = world.run_system_cached(four.pipe(double));920assert!(matches!(output, Ok(8)));921922let output = world.run_system_cached(four.map(|i| i * 2));923assert!(matches!(output, Ok(8)));924}925926#[test]927fn cached_system_into_same_system_type() {928struct Foo;929impl IntoSystem<(), (), ()> for Foo {930type System = ApplyDeferred;931fn into_system(_: Self) -> Self::System {932ApplyDeferred933}934}935936struct Bar;937impl IntoSystem<(), (), ()> for Bar {938type System = ApplyDeferred;939fn into_system(_: Self) -> Self::System {940ApplyDeferred941}942}943944let mut world = World::new();945let foo1 = world.register_system_cached(Foo);946let foo2 = world.register_system_cached(Foo);947let bar1 = world.register_system_cached(Bar);948let bar2 = world.register_system_cached(Bar);949950// The `S: IntoSystem` types are different, so they should be cached951// as separate systems, even though the `<S as IntoSystem>::System`952// types / values are the same (`ApplyDeferred`).953assert_ne!(foo1, bar1);954955// But if the `S: IntoSystem` types are the same, they'll be cached956// as the same system.957assert_eq!(foo1, foo2);958assert_eq!(bar1, bar2);959}960961#[test]962fn system_with_input_ref() {963fn with_ref(InRef(input): InRef<u8>, mut counter: ResMut<Counter>) {964counter.0 += *input;965}966967let mut world = World::new();968world.insert_resource(Counter(0));969970let id = world.register_system(with_ref);971world.run_system_with(id, &2).unwrap();972assert_eq!(*world.resource::<Counter>(), Counter(2));973}974975#[test]976fn system_with_input_mut() {977#[derive(Event)]978struct MyEvent {979cancelled: bool,980}981982fn post(InMut(event): InMut<MyEvent>, counter: ResMut<Counter>) {983if counter.0 > 0 {984event.cancelled = true;985}986}987988let mut world = World::new();989world.insert_resource(Counter(0));990let post_system = world.register_system(post);991992let mut event = MyEvent { cancelled: false };993world.run_system_with(post_system, &mut event).unwrap();994assert!(!event.cancelled);995996world.resource_mut::<Counter>().0 = 1;997world.run_system_with(post_system, &mut event).unwrap();998assert!(event.cancelled);999}10001001#[test]1002fn run_system_invalid_params() {1003use crate::system::RegisteredSystemError;1004use alloc::string::ToString;10051006#[derive(Resource)]1007struct T;10081009fn system(_: Res<T>) {}10101011let mut world = World::new();1012let id = world.register_system(system);1013// This fails because `T` has not been added to the world yet.1014let result = world.run_system(id);10151016assert!(matches!(result, Err(RegisteredSystemError::Failed { .. })));1017let expected = "Resource does not exist";1018let actual = result.unwrap_err().to_string();10191020assert!(1021actual.contains(expected),1022"Expected error message to contain `{}` but got `{}`",1023expected,1024actual1025);1026}10271028#[test]1029fn run_system_recursive() {1030std::thread_local! {1031static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };1032static SYSTEM_ID: Cell<Option<SystemId>> = default();1033}10341035fn system(mut commands: Commands) {1036let count = INVOCATIONS_LEFT.get() - 1;1037INVOCATIONS_LEFT.set(count);1038if count > 0 {1039commands.run_system(SYSTEM_ID.get().unwrap());1040}1041}10421043let mut world = World::new();1044let id = world.register_system(system);1045SYSTEM_ID.set(Some(id));1046world.run_system(id).unwrap();10471048assert_eq!(INVOCATIONS_LEFT.get(), 0);1049}10501051#[test]1052fn run_system_exclusive_adapters() {1053let mut world = World::new();1054fn system(_: &mut World) {}1055world.run_system_cached(system).unwrap();1056world.run_system_cached(system.pipe(system)).unwrap();1057world.run_system_cached(system.map(|()| {})).unwrap();1058}10591060#[test]1061fn wrong_system_type() {1062fn test() -> Result<(), u8> {1063Ok(())1064}10651066let mut world = World::new();10671068let entity = world.register_system_cached(test).entity();10691070match world.run_system::<u8>(SystemId::from_entity(entity)) {1071Ok(_) => panic!("Should fail since called `run_system` with wrong SystemId type."),1072Err(RegisteredSystemError::IncorrectType(_, _)) => (),1073Err(err) => panic!("Failed with wrong error. `{:?}`", err),1074}1075}1076}107710781079