From 57acc53edc6b3f10a6c7e8435ee3e37c91513b03 Mon Sep 17 00:00:00 2001 From: Helix Date: Thu, 9 Nov 2023 15:10:12 +0800 Subject: [PATCH 1/5] Add `run_system_singleton` --- crates/bevy_ecs/src/system/commands/mod.rs | 13 +- crates/bevy_ecs/src/system/system_registry.rs | 142 +++++++++++++++++- 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 89c8493dc667b..750d19f64ba9f 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -14,7 +14,7 @@ pub use command_queue::CommandQueue; pub use parallel_scope::*; use std::marker::PhantomData; -use super::{Deferred, Resource, SystemBuffer, SystemMeta}; +use super::{Deferred, IntoSystem, Resource, RunSystemSingleton, SystemBuffer, SystemMeta}; /// A [`World`] mutation. /// @@ -527,6 +527,17 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(RunSystem::new(id)); } + /// Runs the system by itself. + /// Systems are ran in an exclusive and single threaded way. + /// Running slow systems can become a bottleneck. + /// + /// Calls [`World::run_system_singleton`](crate::system::World::run_system_singleton). + pub fn run_system_singleton(&mut self, system: impl IntoSystem<(), (), M>) { + self.queue.push(RunSystemSingleton::new( + IntoSystem::<(), (), M>::into_system(system), + )); + } + /// Pushes a generic [`Command`] to the command queue. /// /// `command` can be a built-in command, custom struct that implements [`Command`] or a closure diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 40fdc9eeeffe8..d8c307ef283f8 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -1,10 +1,15 @@ +use std::ops::Deref; + use crate::entity::Entity; use crate::system::{BoxedSystem, Command, IntoSystem}; use crate::world::World; use crate::{self as bevy_ecs}; -use bevy_ecs_macros::Component; +use bevy_ecs_macros::{Component, Resource}; +use bevy_utils::tracing::error; use thiserror::Error; +use super::System; + /// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. #[derive(Component)] struct RegisteredSystem { @@ -172,6 +177,73 @@ impl World { } Ok(()) } + + /// Run a system one-shot by itself. + /// It calls like [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once), + /// but behaves like [`World::run_system`]. + /// System will be registered on first run, then its state is kept for after calls. + /// + /// # Limitations + /// + /// Likes [`World::run_system`], with additionally: + /// + /// - Closure systems are not allowed: systems are identified by their name. + pub fn run_system_singleton>( + &mut self, + system: T, + ) -> Result<(), UnregisteredSystemError> { + // create the hashmap if it doesn't exist + let mut system_map = { + let mut map = self.get_resource_mut::(); + if map.is_none() { + self.insert_resource::(SystemMap { + map: bevy_utils::HashMap::default(), + }); + map = self.get_resource_mut::(); + }; + map.unwrap() + }; + + let system = IntoSystem::<(), (), M>::into_system(system); + // use the system name as the key + let system_name = system.name().clone(); + // check if the system is a closure + if system_name.contains("{{closure}}") { + error!("Cannot run along a closure system"); + return Err(UnregisteredSystemError::Closure); + } + // take system out + let RegisteredSystem { + mut initialized, + mut system, + } = system_map + .map + .remove(system_name.deref()) + .unwrap_or(RegisteredSystem { + initialized: false, + system: Box::new(system), + }); + + // run the system + if !initialized { + system.initialize(self); + initialized = true; + } + system.run((), self); + system.apply_deferred(self); + + // put system back + let mut sys_map = self.get_resource_mut::().unwrap(); + sys_map.map.insert( + system_name.into(), + RegisteredSystem { + initialized, + system, + }, + ); + + Ok(()) + } } /// The [`Command`] type for [`World::run_system`]. @@ -213,6 +285,41 @@ pub enum RegisteredSystemError { SelfRemove(SystemId), } +/// The [`Command`] type for [`World::run_system_singleton`]. +/// +/// This command runs systems in an exclusive and single threaded way. +/// Running slow systems can become a bottleneck. +#[derive(Debug, Clone)] +pub struct RunSystemSingleton> { + system: T, +} +impl> RunSystemSingleton { + /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) + pub fn new(system: T) -> Self { + Self { system } + } +} +impl> Command for RunSystemSingleton { + #[inline] + fn apply(self, world: &mut World) { + let _ = world.run_system_singleton(self.system); + } +} + +/// An internal [`Resource`] that stores all systems called by [`World::run_system_singleton`]. +#[derive(Resource)] +struct SystemMap { + map: bevy_utils::HashMap, +} + +/// An operation with [`World::run_system_singleton`] systems failed. +#[derive(Debug, Error)] +pub enum UnregisteredSystemError { + /// Closure systems do not have a unique name. + #[error("RunSystemAlong: System was a closure")] + Closure, +} + mod tests { use crate as bevy_ecs; use crate::prelude::*; @@ -249,6 +356,17 @@ mod tests { world.resource_mut::().set_changed(); let _ = world.run_system(id); assert_eq!(*world.resource::(), Counter(2)); + + // Test for `run_system_singleton` + let _ = world.run_system_singleton(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(3)); + // Nothing changed + let _ = world.run_system_singleton(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(3)); + // Making a change + world.resource_mut::().set_changed(); + let _ = world.run_system_singleton(count_up_iff_changed); + assert_eq!(*world.resource::(), Counter(4)); } #[test] @@ -271,6 +389,14 @@ mod tests { assert_eq!(*world.resource::(), Counter(4)); let _ = world.run_system(id); assert_eq!(*world.resource::(), Counter(8)); + + // Test for `run_system_singleton` + let _ = world.run_system_singleton(doubling); + assert_eq!(*world.resource::(), Counter(8)); + let _ = world.run_system_singleton(doubling); + assert_eq!(*world.resource::(), Counter(16)); + let _ = world.run_system_singleton(doubling); + assert_eq!(*world.resource::(), Counter(32)); } #[test] @@ -302,4 +428,18 @@ mod tests { let _ = world.run_system(nested_id); assert_eq!(*world.resource::(), Counter(5)); } + + #[test] + fn closure_systems() { + let mut world = World::new(); + world.insert_resource(Counter(0)); + + let count = |mut counter: ResMut| { + counter.0 += 1; + }; + + // should fail when call a closure + let result = world.run_system_singleton(count); + assert!(result.is_err()); + } } From f715358cc2c5cd35361a3d32c048b8d3a04ab3fb Mon Sep 17 00:00:00 2001 From: Helix Date: Thu, 9 Nov 2023 21:39:22 +0800 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Giacomo Stevanato --- crates/bevy_ecs/src/system/system_registry.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index d8c307ef283f8..5c769cc6b169a 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -193,16 +193,9 @@ impl World { system: T, ) -> Result<(), UnregisteredSystemError> { // create the hashmap if it doesn't exist - let mut system_map = { - let mut map = self.get_resource_mut::(); - if map.is_none() { - self.insert_resource::(SystemMap { - map: bevy_utils::HashMap::default(), - }); - map = self.get_resource_mut::(); - }; - map.unwrap() - }; + let mut system_map = self.get_resource_or_init_with(|| SystemMap { + map: bevy_utils::HashMap::default(), + }); let system = IntoSystem::<(), (), M>::into_system(system); // use the system name as the key @@ -219,7 +212,7 @@ impl World { } = system_map .map .remove(system_name.deref()) - .unwrap_or(RegisteredSystem { + .unwrap_or_else(|| RegisteredSystem { initialized: false, system: Box::new(system), }); From af94d070ad02a90afb2c88fe49890ff7323c6e34 Mon Sep 17 00:00:00 2001 From: Helix Date: Thu, 9 Nov 2023 22:25:14 +0800 Subject: [PATCH 3/5] Improve `World::run_system_singleton` as suggested --- crates/bevy_ecs/src/system/system_registry.rs | 131 ++++++++++++------ 1 file changed, 85 insertions(+), 46 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 5c769cc6b169a..c05640a8cae61 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -1,11 +1,8 @@ -use std::ops::Deref; - use crate::entity::Entity; use crate::system::{BoxedSystem, Command, IntoSystem}; use crate::world::World; use crate::{self as bevy_ecs}; use bevy_ecs_macros::{Component, Resource}; -use bevy_utils::tracing::error; use thiserror::Error; use super::System; @@ -17,6 +14,12 @@ struct RegisteredSystem { system: BoxedSystem, } +/// A wrapper for [`BoxedSystem`] used by [`World::run_system_singleton`]. The system will be taken while running. +struct UnregisteredSystem { + initialized: bool, + system: Option, +} + /// A system that has been removed from the registry. /// It contains the system and whether or not it has been initialized. /// @@ -185,37 +188,42 @@ impl World { /// /// # Limitations /// - /// Likes [`World::run_system`], with additionally: - /// - /// - Closure systems are not allowed: systems are identified by their name. + /// See [`World::run_system`]. pub fn run_system_singleton>( &mut self, system: T, ) -> Result<(), UnregisteredSystemError> { // create the hashmap if it doesn't exist - let mut system_map = self.get_resource_or_init_with(|| SystemMap { + let mut system_map = self.get_resource_or_insert_with(|| SystemMap { map: bevy_utils::HashMap::default(), }); let system = IntoSystem::<(), (), M>::into_system(system); - // use the system name as the key - let system_name = system.name().clone(); - // check if the system is a closure - if system_name.contains("{{closure}}") { - error!("Cannot run along a closure system"); - return Err(UnregisteredSystemError::Closure); - } + // use the system type id as the key + let system_type_id = system.type_id(); // take system out - let RegisteredSystem { + let UnregisteredSystem { mut initialized, - mut system, + system, } = system_map .map - .remove(system_name.deref()) - .unwrap_or_else(|| RegisteredSystem { + .remove(&system_type_id) + .unwrap_or_else(|| UnregisteredSystem { initialized: false, - system: Box::new(system), + system: Some(Box::new(system)), }); + // insert the marker + system_map.map.insert( + system_type_id, + UnregisteredSystem { + initialized: false, + system: None, + }, + ); + // check if runs recursively + let Some(mut system) = system else { + return Err(UnregisteredSystemError::Recursive); + }; // run the system if !initialized { @@ -225,15 +233,16 @@ impl World { system.run((), self); system.apply_deferred(self); - // put system back - let mut sys_map = self.get_resource_mut::().unwrap(); - sys_map.map.insert( - system_name.into(), - RegisteredSystem { - initialized, - system, - }, - ); + // put system back if resource still exists + if let Some(mut sys_map) = self.get_resource_mut::() { + sys_map.map.insert( + system_type_id, + UnregisteredSystem { + initialized, + system: Some(system), + }, + ); + }; Ok(()) } @@ -302,15 +311,15 @@ impl> Command for RunSystemSingleton { /// An internal [`Resource`] that stores all systems called by [`World::run_system_singleton`]. #[derive(Resource)] struct SystemMap { - map: bevy_utils::HashMap, + map: bevy_utils::HashMap, } /// An operation with [`World::run_system_singleton`] systems failed. #[derive(Debug, Error)] pub enum UnregisteredSystemError { - /// Closure systems do not have a unique name. - #[error("RunSystemAlong: System was a closure")] - Closure, + /// A system tried to run itself recursively. + #[error("RunSystemAlong: System tried to run itself recursively")] + Recursive, } mod tests { @@ -360,6 +369,30 @@ mod tests { world.resource_mut::().set_changed(); let _ = world.run_system_singleton(count_up_iff_changed); assert_eq!(*world.resource::(), Counter(4)); + + // Test for `run_system_singleton` with closure + // This is needed for closure's `TypeId` is related to its occurrence + fn run_count_up_changed_indirect( + world: &mut World, + ) -> Result<(), crate::system::UnregisteredSystemError> { + world.run_system_singleton( + |mut counter: ResMut, change_detector: ResMut| { + if change_detector.is_changed() { + counter.0 += 1; + } + }, + ) + } + + let _ = run_count_up_changed_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(5)); + // Nothing changed + let _ = run_count_up_changed_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(5)); + // Making a change + world.resource_mut::().set_changed(); + let _ = run_count_up_changed_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(6)); } #[test] @@ -390,6 +423,26 @@ mod tests { assert_eq!(*world.resource::(), Counter(16)); let _ = world.run_system_singleton(doubling); assert_eq!(*world.resource::(), Counter(32)); + + // Test for `run_system_singleton` with closure + // This is needed for closure's `TypeId` is related to its occurrence + fn run_doubling_indirect( + world: &mut World, + ) -> Result<(), crate::system::UnregisteredSystemError> { + world.run_system_singleton( + |last_counter: Local, mut counter: ResMut| { + counter.0 += last_counter.0 .0; + last_counter.0 .0 = counter.0; + }, + ) + } + + let _ = run_doubling_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(32)); + let _ = run_doubling_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(64)); + let _ = run_doubling_indirect(&mut world); + assert_eq!(*world.resource::(), Counter(128)); } #[test] @@ -421,18 +474,4 @@ mod tests { let _ = world.run_system(nested_id); assert_eq!(*world.resource::(), Counter(5)); } - - #[test] - fn closure_systems() { - let mut world = World::new(); - world.insert_resource(Counter(0)); - - let count = |mut counter: ResMut| { - counter.0 += 1; - }; - - // should fail when call a closure - let result = world.run_system_singleton(count); - assert!(result.is_err()); - } } From 63f3dec35720677fb4a0defc979c496354f96b0e Mon Sep 17 00:00:00 2001 From: Helix Date: Fri, 10 Nov 2023 18:53:55 +0800 Subject: [PATCH 4/5] Apply suggestion, and more detailed documentation. --- crates/bevy_ecs/src/system/commands/mod.rs | 9 +- crates/bevy_ecs/src/system/system_registry.rs | 85 +++++++++++-------- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 750d19f64ba9f..ee47f3f6d99da 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -527,11 +527,16 @@ impl<'w, 's> Commands<'w, 's> { self.queue.push(RunSystem::new(id)); } - /// Runs the system by itself. + /// Runs the system by referring it. This method will register the system newly called with, + /// whether you have registered or not. Different calls with same system will share same state. + /// + /// Specially note when running closures with capture, the captured value won't update after first call. + /// Examples see [`World::run_system_singleton`]. Register them each, use [`World::run_system`] instead. + /// /// Systems are ran in an exclusive and single threaded way. /// Running slow systems can become a bottleneck. /// - /// Calls [`World::run_system_singleton`](crate::system::World::run_system_singleton). + /// Calls [`World::run_system_singleton`]. pub fn run_system_singleton(&mut self, system: impl IntoSystem<(), (), M>) { self.queue.push(RunSystemSingleton::new( IntoSystem::<(), (), M>::into_system(system), diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index c05640a8cae61..a7a00463b4d86 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -3,6 +3,7 @@ use crate::system::{BoxedSystem, Command, IntoSystem}; use crate::world::World; use crate::{self as bevy_ecs}; use bevy_ecs_macros::{Component, Resource}; +use bevy_utils::tracing::info; use thiserror::Error; use super::System; @@ -189,16 +190,35 @@ impl World { /// # Limitations /// /// See [`World::run_system`]. + /// + /// # Attention + /// + /// Please note that closure is identified by its **occurrence** no matter what it captures, + /// thus this code will call the first cached one rather than actual passed one. + /// + /// That means, codes like below will not work as expected. + /// + /// ```rust + /// use bevy_ecs::prelude::World; + /// let mut world = World::new(); + /// // This will creates closures regarded as one same closure + /// let make_system = |n| move || println!("{n}"); + /// let _ = world.run_system_singleton(make_system(0)); // prints 0 + /// let _ = world.run_system_singleton(make_system(1)); // prints 0 same + /// ``` pub fn run_system_singleton>( &mut self, system: T, ) -> Result<(), UnregisteredSystemError> { // create the hashmap if it doesn't exist - let mut system_map = self.get_resource_or_insert_with(|| SystemMap { - map: bevy_utils::HashMap::default(), - }); + let mut system_map = self.get_resource_or_insert_with(|| SystemMap::default()); let system = IntoSystem::<(), (), M>::into_system(system); + + // check captures of closure + if std::mem::size_of::() != 0 { + info!(target: "run_system_singleton", "Closure with capture(s) runs, be sure that you're not relying on differently captured versions of one closure.") + } // use the system type id as the key let system_type_id = system.type_id(); // take system out @@ -234,8 +254,8 @@ impl World { system.apply_deferred(self); // put system back if resource still exists - if let Some(mut sys_map) = self.get_resource_mut::() { - sys_map.map.insert( + if let Some(mut system_map) = self.get_resource_mut::() { + system_map.map.insert( system_type_id, UnregisteredSystem { initialized, @@ -313,12 +333,19 @@ impl> Command for RunSystemSingleton { struct SystemMap { map: bevy_utils::HashMap, } +impl Default for SystemMap { + fn default() -> Self { + Self { + map: bevy_utils::HashMap::default(), + } + } +} /// An operation with [`World::run_system_singleton`] systems failed. #[derive(Debug, Error)] pub enum UnregisteredSystemError { /// A system tried to run itself recursively. - #[error("RunSystemAlong: System tried to run itself recursively")] + #[error("RunSystemSingleton: System tried to run itself recursively")] Recursive, } @@ -371,27 +398,21 @@ mod tests { assert_eq!(*world.resource::(), Counter(4)); // Test for `run_system_singleton` with closure - // This is needed for closure's `TypeId` is related to its occurrence - fn run_count_up_changed_indirect( - world: &mut World, - ) -> Result<(), crate::system::UnregisteredSystemError> { - world.run_system_singleton( - |mut counter: ResMut, change_detector: ResMut| { - if change_detector.is_changed() { - counter.0 += 1; - } - }, - ) - } - - let _ = run_count_up_changed_indirect(&mut world); + let run_count_up_changed_closure = + |mut counter: ResMut, change_detector: ResMut| { + if change_detector.is_changed() { + counter.0 += 1; + } + }; + + let _ = world.run_system_singleton(run_count_up_changed_closure); assert_eq!(*world.resource::(), Counter(5)); // Nothing changed - let _ = run_count_up_changed_indirect(&mut world); + let _ = world.run_system_singleton(run_count_up_changed_closure); assert_eq!(*world.resource::(), Counter(5)); // Making a change world.resource_mut::().set_changed(); - let _ = run_count_up_changed_indirect(&mut world); + let _ = world.run_system_singleton(run_count_up_changed_closure); assert_eq!(*world.resource::(), Counter(6)); } @@ -426,22 +447,16 @@ mod tests { // Test for `run_system_singleton` with closure // This is needed for closure's `TypeId` is related to its occurrence - fn run_doubling_indirect( - world: &mut World, - ) -> Result<(), crate::system::UnregisteredSystemError> { - world.run_system_singleton( - |last_counter: Local, mut counter: ResMut| { - counter.0 += last_counter.0 .0; - last_counter.0 .0 = counter.0; - }, - ) - } + let doubling_closure = |last_counter: Local, mut counter: ResMut| { + counter.0 += last_counter.0 .0; + last_counter.0 .0 = counter.0; + }; - let _ = run_doubling_indirect(&mut world); + let _ = world.run_system_singleton(doubling_closure); assert_eq!(*world.resource::(), Counter(32)); - let _ = run_doubling_indirect(&mut world); + let _ = world.run_system_singleton(doubling_closure); assert_eq!(*world.resource::(), Counter(64)); - let _ = run_doubling_indirect(&mut world); + let _ = world.run_system_singleton(doubling_closure); assert_eq!(*world.resource::(), Counter(128)); } From 5e8737e27a9535d5dd9b07c998eaeb55ee7d4ac5 Mon Sep 17 00:00:00 2001 From: Helix Date: Sun, 12 Nov 2023 16:55:32 +0800 Subject: [PATCH 5/5] Apply suggestions --- crates/bevy_ecs/src/system/commands/mod.rs | 2 +- crates/bevy_ecs/src/system/system_registry.rs | 114 +++++++++++++----- 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ee47f3f6d99da..1a4dd37e616fa 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -529,7 +529,7 @@ impl<'w, 's> Commands<'w, 's> { /// Runs the system by referring it. This method will register the system newly called with, /// whether you have registered or not. Different calls with same system will share same state. - /// + /// /// Specially note when running closures with capture, the captured value won't update after first call. /// Examples see [`World::run_system_singleton`]. Register them each, use [`World::run_system`] instead. /// diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index a7a00463b4d86..2e0525fb91144 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -189,35 +189,108 @@ impl World { /// /// # Limitations /// - /// See [`World::run_system`]. + /// - Stored systems cannot be chained. + /// - Stored systems cannot be recursive. + /// - Exclusive systems cannot be used. + /// - Closures with different captures cannot be distinguished, they share a same [`TypeId`](std::any::TypeId). + /// + /// # Examples + /// + /// In most cases, use this method like using [`World::run_system`] with a unique id for each system. + /// + /// Persistent state: + /// + /// ```rust + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Resource, Default)] + /// struct Counter(u8); + /// + /// fn increment(mut counter: Local) { + /// counter.0 += 1; + /// println!("{}", counter.0); + /// } + /// + /// let mut world = World::default(); + /// world.run_system_singleton(increment); // -> 1 + /// world.run_system_singleton(increment); // -> 2 + /// + /// // Each closure has its own state + /// for _ in 0..5 { // -> 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 + /// world.run_system_singleton(|mut counter: Local| { + /// counter.0 += 1; + /// println!("{}", counter.0); + /// }); + /// world.run_system_singleton(|mut counter: Local| { + /// counter.0 += 1; + /// println!("{}", counter.0); + /// }); + /// } + /// + /// // Store it if you want to share state between calls + /// let increment_closure = |mut counter: Local| { + /// counter.0 += 1; + /// }; + /// ``` + /// + /// Change detection: + /// + /// ```rust + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Resource, Default)] + /// struct ChangeDetector; + /// + /// let mut world = World::default(); + /// world.init_resource::(); + /// + /// let detector = |change_detector: ResMut| { + /// if change_detector.is_changed() { + /// println!("Something happened!"); + /// } else { + /// println!("Nothing happened."); + /// } + /// }; + /// + /// // Resources are changed when they are first added + /// let _ = world.run_system_singleton(detector); // -> Something happened! + /// let _ = world.run_system_singleton(detector); // -> Nothing happened. + /// world.resource_mut::().set_changed(); + /// let _ = world.run_system_singleton(detector); // -> Something happened! + /// ``` /// /// # Attention /// /// Please note that closure is identified by its **occurrence** no matter what it captures, - /// thus this code will call the first cached one rather than actual passed one. - /// - /// That means, codes like below will not work as expected. + /// This code will call the first cached one rather than actual passed one. /// /// ```rust /// use bevy_ecs::prelude::World; /// let mut world = World::new(); - /// // This will creates closures regarded as one same closure + /// + /// // Captures will not make a closure differed /// let make_system = |n| move || println!("{n}"); - /// let _ = world.run_system_singleton(make_system(0)); // prints 0 - /// let _ = world.run_system_singleton(make_system(1)); // prints 0 same + /// let _ = world.run_system_singleton(make_system(0)); // -> 0 + /// let _ = world.run_system_singleton(make_system(1)); // -> 0 + /// + /// // Register them and `run_system` instead + /// let sys0 = world.register_system(make_system(0)); + /// let sys1 = world.register_system(make_system(1)); + /// let _ = world.run_system(sys0); // -> 0 + /// let _ = world.run_system(sys1); // -> 1 /// ``` pub fn run_system_singleton>( &mut self, system: T, ) -> Result<(), UnregisteredSystemError> { // create the hashmap if it doesn't exist - let mut system_map = self.get_resource_or_insert_with(|| SystemMap::default()); + let mut system_map = self.get_resource_or_insert_with(SystemMap::default); let system = IntoSystem::<(), (), M>::into_system(system); // check captures of closure if std::mem::size_of::() != 0 { - info!(target: "run_system_singleton", "Closure with capture(s) runs, be sure that you're not relying on differently captured versions of one closure.") + info!(target: "run_system_singleton", "Closure with capture(s) runs, be sure that you're not relying on differently captured versions of one closure."); } // use the system type id as the key let system_type_id = system.type_id(); @@ -227,21 +300,13 @@ impl World { system, } = system_map .map - .remove(&system_type_id) - .unwrap_or_else(|| UnregisteredSystem { + .entry(system_type_id) + .or_insert_with(|| UnregisteredSystem { initialized: false, system: Some(Box::new(system)), }); - // insert the marker - system_map.map.insert( - system_type_id, - UnregisteredSystem { - initialized: false, - system: None, - }, - ); // check if runs recursively - let Some(mut system) = system else { + let Some(mut system) = system.take() else { return Err(UnregisteredSystemError::Recursive); }; @@ -329,17 +394,10 @@ impl> Command for RunSystemSingleton { } /// An internal [`Resource`] that stores all systems called by [`World::run_system_singleton`]. -#[derive(Resource)] +#[derive(Resource, Default)] struct SystemMap { map: bevy_utils::HashMap, } -impl Default for SystemMap { - fn default() -> Self { - Self { - map: bevy_utils::HashMap::default(), - } - } -} /// An operation with [`World::run_system_singleton`] systems failed. #[derive(Debug, Error)]