bevy_ecs/system/
system_registry.rs

1use crate::entity::Entity;
2use crate::system::{BoxedSystem, IntoSystem};
3use crate::world::{Command, World};
4use crate::{self as bevy_ecs};
5use bevy_ecs_macros::Component;
6use thiserror::Error;
7
8/// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized.
9#[derive(Component)]
10struct RegisteredSystem<I, O> {
11    initialized: bool,
12    system: BoxedSystem<I, O>,
13}
14
15/// A system that has been removed from the registry.
16/// It contains the system and whether or not it has been initialized.
17///
18/// This struct is returned by [`World::remove_system`].
19pub struct RemovedSystem<I = (), O = ()> {
20    initialized: bool,
21    system: BoxedSystem<I, O>,
22}
23
24impl<I, O> RemovedSystem<I, O> {
25    /// Is the system initialized?
26    /// A system is initialized the first time it's ran.
27    pub fn initialized(&self) -> bool {
28        self.initialized
29    }
30
31    /// The system removed from the storage.
32    pub fn system(self) -> BoxedSystem<I, O> {
33        self.system
34    }
35}
36
37/// An identifier for a registered system.
38///
39/// These are opaque identifiers, keyed to a specific [`World`],
40/// and are created via [`World::register_system`].
41pub struct SystemId<I = (), O = ()> {
42    pub(crate) entity: Entity,
43    pub(crate) marker: std::marker::PhantomData<fn(I) -> O>,
44}
45
46impl<I, O> SystemId<I, O> {
47    /// Transforms a [`SystemId`] into the [`Entity`] that holds the one-shot system's state.
48    ///
49    /// It's trivial to convert [`SystemId`] into an [`Entity`] since a one-shot system
50    /// is really an entity with associated handler function.
51    ///
52    /// For example, this is useful if you want to assign a name label to a system.
53    pub fn entity(self) -> Entity {
54        self.entity
55    }
56
57    /// Create [`SystemId`] from an [`Entity`]. Useful when you only have entity handles to avoid
58    /// adding extra components that have a [`SystemId`] everywhere. To run a system with this ID
59    ///  - The entity must be a system
60    ///  - The `I` + `O` types must be correct
61    pub fn from_entity(entity: Entity) -> Self {
62        Self {
63            entity,
64            marker: std::marker::PhantomData,
65        }
66    }
67}
68
69impl<I, O> Eq for SystemId<I, O> {}
70
71// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
72impl<I, O> Copy for SystemId<I, O> {}
73
74// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
75impl<I, O> Clone for SystemId<I, O> {
76    fn clone(&self) -> Self {
77        *self
78    }
79}
80
81// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
82impl<I, O> PartialEq for SystemId<I, O> {
83    fn eq(&self, other: &Self) -> bool {
84        self.entity == other.entity && self.marker == other.marker
85    }
86}
87
88// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters.
89impl<I, O> std::hash::Hash for SystemId<I, O> {
90    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
91        self.entity.hash(state);
92    }
93}
94
95impl<I, O> std::fmt::Debug for SystemId<I, O> {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_tuple("SystemId")
98            .field(&self.entity)
99            .field(&self.entity)
100            .finish()
101    }
102}
103
104impl World {
105    /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
106    ///
107    /// It's possible to register the same systems more than once, they'll be stored separately.
108    ///
109    /// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule),
110    /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.
111    /// This allows for running systems in a pushed-based fashion.
112    /// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases
113    /// due to its better performance and ability to run non-conflicting systems simultaneously.
114    pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
115        &mut self,
116        system: S,
117    ) -> SystemId<I, O> {
118        self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
119    }
120
121    /// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].
122    ///
123    ///  This is useful if the [`IntoSystem`] implementor has already been turned into a
124    /// [`System`](crate::system::System) trait object and put in a [`Box`].
125    pub fn register_boxed_system<I: 'static, O: 'static>(
126        &mut self,
127        system: BoxedSystem<I, O>,
128    ) -> SystemId<I, O> {
129        SystemId {
130            entity: self
131                .spawn(RegisteredSystem {
132                    initialized: false,
133                    system,
134                })
135                .id(),
136            marker: std::marker::PhantomData,
137        }
138    }
139
140    /// Removes a registered system and returns the system, if it exists.
141    /// After removing a system, the [`SystemId`] becomes invalid and attempting to use it afterwards will result in errors.
142    /// Re-adding the removed system will register it on a new [`SystemId`].
143    ///
144    /// If no system corresponds to the given [`SystemId`], this method returns an error.
145    /// Systems are also not allowed to remove themselves, this returns an error too.
146    pub fn remove_system<I: 'static, O: 'static>(
147        &mut self,
148        id: SystemId<I, O>,
149    ) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>> {
150        match self.get_entity_mut(id.entity) {
151            Some(mut entity) => {
152                let registered_system = entity
153                    .take::<RegisteredSystem<I, O>>()
154                    .ok_or(RegisteredSystemError::SelfRemove(id))?;
155                entity.despawn();
156                Ok(RemovedSystem {
157                    initialized: registered_system.initialized,
158                    system: registered_system.system,
159                })
160            }
161            None => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
162        }
163    }
164
165    /// Run stored systems by their [`SystemId`].
166    /// Before running a system, it must first be registered.
167    /// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
168    /// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
169    /// because it keeps local state between calls and change detection works correctly.
170    ///
171    /// In order to run a chained system with an input, use [`World::run_system_with_input`] instead.
172    ///
173    /// # Limitations
174    ///
175    ///  - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
176    ///
177    /// # Examples
178    ///
179    /// ## Running a system
180    ///
181    /// ```
182    /// # use bevy_ecs::prelude::*;
183    /// fn increment(mut counter: Local<u8>) {
184    ///    *counter += 1;
185    ///    println!("{}", *counter);
186    /// }
187    ///
188    /// let mut world = World::default();
189    /// let counter_one = world.register_system(increment);
190    /// let counter_two = world.register_system(increment);
191    /// world.run_system(counter_one); // -> 1
192    /// world.run_system(counter_one); // -> 2
193    /// world.run_system(counter_two); // -> 1
194    /// ```
195    ///
196    /// ## Change detection
197    ///
198    /// ```
199    /// # use bevy_ecs::prelude::*;
200    /// #[derive(Resource, Default)]
201    /// struct ChangeDetector;
202    ///
203    /// let mut world = World::default();
204    /// world.init_resource::<ChangeDetector>();
205    /// let detector = world.register_system(|change_detector: ResMut<ChangeDetector>| {
206    ///     if change_detector.is_changed() {
207    ///         println!("Something happened!");
208    ///     } else {
209    ///         println!("Nothing happened.");
210    ///     }
211    /// });
212    ///
213    /// // Resources are changed when they are first added
214    /// let _ = world.run_system(detector); // -> Something happened!
215    /// let _ = world.run_system(detector); // -> Nothing happened.
216    /// world.resource_mut::<ChangeDetector>().set_changed();
217    /// let _ = world.run_system(detector); // -> Something happened!
218    /// ```
219    ///
220    /// ## Getting system output
221    ///
222    /// ```
223    /// # use bevy_ecs::prelude::*;
224    ///
225    /// #[derive(Resource)]
226    /// struct PlayerScore(i32);
227    ///
228    /// #[derive(Resource)]
229    /// struct OpponentScore(i32);
230    ///
231    /// fn get_player_score(player_score: Res<PlayerScore>) -> i32 {
232    ///   player_score.0
233    /// }
234    ///
235    /// fn get_opponent_score(opponent_score: Res<OpponentScore>) -> i32 {
236    ///   opponent_score.0
237    /// }
238    ///
239    /// let mut world = World::default();
240    /// world.insert_resource(PlayerScore(3));
241    /// world.insert_resource(OpponentScore(2));
242    ///
243    /// let scoring_systems = [
244    ///   ("player", world.register_system(get_player_score)),
245    ///   ("opponent", world.register_system(get_opponent_score)),
246    /// ];
247    ///
248    /// for (label, scoring_system) in scoring_systems {
249    ///   println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded"));
250    /// }
251    /// ```
252    pub fn run_system<O: 'static>(
253        &mut self,
254        id: SystemId<(), O>,
255    ) -> Result<O, RegisteredSystemError<(), O>> {
256        self.run_system_with_input(id, ())
257    }
258
259    /// Run a stored chained system by its [`SystemId`], providing an input value.
260    /// Before running a system, it must first be registered.
261    /// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
262    ///
263    /// # Limitations
264    ///
265    ///  - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// # use bevy_ecs::prelude::*;
271    /// fn increment(In(increment_by): In<u8>, mut counter: Local<u8>) -> u8 {
272    ///   *counter += increment_by;
273    ///   *counter
274    /// }
275    ///
276    /// let mut world = World::default();
277    /// let counter_one = world.register_system(increment);
278    /// let counter_two = world.register_system(increment);
279    /// assert_eq!(world.run_system_with_input(counter_one, 1).unwrap(), 1);
280    /// assert_eq!(world.run_system_with_input(counter_one, 20).unwrap(), 21);
281    /// assert_eq!(world.run_system_with_input(counter_two, 30).unwrap(), 30);
282    /// ```
283    ///
284    /// See [`World::run_system`] for more examples.
285    pub fn run_system_with_input<I: 'static, O: 'static>(
286        &mut self,
287        id: SystemId<I, O>,
288        input: I,
289    ) -> Result<O, RegisteredSystemError<I, O>> {
290        // lookup
291        let mut entity = self
292            .get_entity_mut(id.entity)
293            .ok_or(RegisteredSystemError::SystemIdNotRegistered(id))?;
294
295        // take ownership of system trait object
296        let RegisteredSystem {
297            mut initialized,
298            mut system,
299        } = entity
300            .take::<RegisteredSystem<I, O>>()
301            .ok_or(RegisteredSystemError::Recursive(id))?;
302
303        // run the system
304        if !initialized {
305            system.initialize(self);
306            initialized = true;
307        }
308        let result = system.run(input, self);
309
310        // return ownership of system trait object (if entity still exists)
311        if let Some(mut entity) = self.get_entity_mut(id.entity) {
312            entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
313                initialized,
314                system,
315            });
316        }
317        Ok(result)
318    }
319}
320
321/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with_input`].
322///
323/// This command runs systems in an exclusive and single threaded way.
324/// Running slow systems can become a bottleneck.
325///
326/// If the system needs an [`In<_>`](crate::system::In) input value to run, it must
327/// be provided as part of the command.
328///
329/// There is no way to get the output of a system when run as a command, because the
330/// execution of the system happens later. To get the output of a system, use
331/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command.
332#[derive(Debug, Clone)]
333pub struct RunSystemWithInput<I: 'static> {
334    system_id: SystemId<I>,
335    input: I,
336}
337
338/// The [`Command`] type for [`World::run_system`].
339///
340/// This command runs systems in an exclusive and single threaded way.
341/// Running slow systems can become a bottleneck.
342///
343/// If the system needs an [`In<_>`](crate::system::In) input value to run, use the
344/// [`RunSystemWithInput`] type instead.
345///
346/// There is no way to get the output of a system when run as a command, because the
347/// execution of the system happens later. To get the output of a system, use
348/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command.
349pub type RunSystem = RunSystemWithInput<()>;
350
351impl RunSystem {
352    /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands)
353    pub fn new(system_id: SystemId) -> Self {
354        Self::new_with_input(system_id, ())
355    }
356}
357
358impl<I: 'static> RunSystemWithInput<I> {
359    /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands)
360    /// in order to run the specified system with the provided [`In<_>`](crate::system::In) input value.
361    pub fn new_with_input(system_id: SystemId<I>, input: I) -> Self {
362        Self { system_id, input }
363    }
364}
365
366impl<I: 'static + Send> Command for RunSystemWithInput<I> {
367    #[inline]
368    fn apply(self, world: &mut World) {
369        let _ = world.run_system_with_input(self.system_id, self.input);
370    }
371}
372
373/// The [`Command`] type for registering one shot systems from [Commands](crate::system::Commands).
374///
375/// This command needs an already boxed system to register, and an already spawned entity
376pub struct RegisterSystem<I: 'static, O: 'static> {
377    system: BoxedSystem<I, O>,
378    entity: Entity,
379}
380
381impl<I: 'static, O: 'static> RegisterSystem<I, O> {
382    /// Creates a new [Command] struct, which can be added to [Commands](crate::system::Commands)
383    pub fn new<M, S: IntoSystem<I, O, M> + 'static>(system: S, entity: Entity) -> Self {
384        Self {
385            system: Box::new(IntoSystem::into_system(system)),
386            entity,
387        }
388    }
389}
390
391impl<I: 'static + Send, O: 'static + Send> Command for RegisterSystem<I, O> {
392    fn apply(self, world: &mut World) {
393        let _ = world.get_entity_mut(self.entity).map(|mut entity| {
394            entity.insert(RegisteredSystem {
395                initialized: false,
396                system: self.system,
397            });
398        });
399    }
400}
401
402/// An operation with stored systems failed.
403#[derive(Error)]
404pub enum RegisteredSystemError<I = (), O = ()> {
405    /// A system was run by id, but no system with that id was found.
406    ///
407    /// Did you forget to register it?
408    #[error("System {0:?} was not registered")]
409    SystemIdNotRegistered(SystemId<I, O>),
410    /// A system tried to run itself recursively.
411    #[error("System {0:?} tried to run itself recursively")]
412    Recursive(SystemId<I, O>),
413    /// A system tried to remove itself.
414    #[error("System {0:?} tried to remove itself")]
415    SelfRemove(SystemId<I, O>),
416}
417
418impl<I, O> std::fmt::Debug for RegisteredSystemError<I, O> {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        match self {
421            Self::SystemIdNotRegistered(arg0) => {
422                f.debug_tuple("SystemIdNotRegistered").field(arg0).finish()
423            }
424            Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(),
425            Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(),
426        }
427    }
428}
429
430mod tests {
431    use crate as bevy_ecs;
432    use crate::prelude::*;
433
434    #[derive(Resource, Default, PartialEq, Debug)]
435    struct Counter(u8);
436
437    #[test]
438    fn change_detection() {
439        #[derive(Resource, Default)]
440        struct ChangeDetector;
441
442        fn count_up_iff_changed(
443            mut counter: ResMut<Counter>,
444            change_detector: ResMut<ChangeDetector>,
445        ) {
446            if change_detector.is_changed() {
447                counter.0 += 1;
448            }
449        }
450
451        let mut world = World::new();
452        world.init_resource::<ChangeDetector>();
453        world.init_resource::<Counter>();
454        assert_eq!(*world.resource::<Counter>(), Counter(0));
455        // Resources are changed when they are first added.
456        let id = world.register_system(count_up_iff_changed);
457        world.run_system(id).expect("system runs successfully");
458        assert_eq!(*world.resource::<Counter>(), Counter(1));
459        // Nothing changed
460        world.run_system(id).expect("system runs successfully");
461        assert_eq!(*world.resource::<Counter>(), Counter(1));
462        // Making a change
463        world.resource_mut::<ChangeDetector>().set_changed();
464        world.run_system(id).expect("system runs successfully");
465        assert_eq!(*world.resource::<Counter>(), Counter(2));
466    }
467
468    #[test]
469    fn local_variables() {
470        // The `Local` begins at the default value of 0
471        fn doubling(last_counter: Local<Counter>, mut counter: ResMut<Counter>) {
472            counter.0 += last_counter.0 .0;
473            last_counter.0 .0 = counter.0;
474        }
475
476        let mut world = World::new();
477        world.insert_resource(Counter(1));
478        assert_eq!(*world.resource::<Counter>(), Counter(1));
479        let id = world.register_system(doubling);
480        world.run_system(id).expect("system runs successfully");
481        assert_eq!(*world.resource::<Counter>(), Counter(1));
482        world.run_system(id).expect("system runs successfully");
483        assert_eq!(*world.resource::<Counter>(), Counter(2));
484        world.run_system(id).expect("system runs successfully");
485        assert_eq!(*world.resource::<Counter>(), Counter(4));
486        world.run_system(id).expect("system runs successfully");
487        assert_eq!(*world.resource::<Counter>(), Counter(8));
488    }
489
490    #[test]
491    fn input_values() {
492        // Verify that a non-Copy, non-Clone type can be passed in.
493        struct NonCopy(u8);
494
495        fn increment_sys(In(NonCopy(increment_by)): In<NonCopy>, mut counter: ResMut<Counter>) {
496            counter.0 += increment_by;
497        }
498
499        let mut world = World::new();
500
501        let id = world.register_system(increment_sys);
502
503        // Insert the resource after registering the system.
504        world.insert_resource(Counter(1));
505        assert_eq!(*world.resource::<Counter>(), Counter(1));
506
507        world
508            .run_system_with_input(id, NonCopy(1))
509            .expect("system runs successfully");
510        assert_eq!(*world.resource::<Counter>(), Counter(2));
511
512        world
513            .run_system_with_input(id, NonCopy(1))
514            .expect("system runs successfully");
515        assert_eq!(*world.resource::<Counter>(), Counter(3));
516
517        world
518            .run_system_with_input(id, NonCopy(20))
519            .expect("system runs successfully");
520        assert_eq!(*world.resource::<Counter>(), Counter(23));
521
522        world
523            .run_system_with_input(id, NonCopy(1))
524            .expect("system runs successfully");
525        assert_eq!(*world.resource::<Counter>(), Counter(24));
526    }
527
528    #[test]
529    fn output_values() {
530        // Verify that a non-Copy, non-Clone type can be returned.
531        #[derive(Eq, PartialEq, Debug)]
532        struct NonCopy(u8);
533
534        fn increment_sys(mut counter: ResMut<Counter>) -> NonCopy {
535            counter.0 += 1;
536            NonCopy(counter.0)
537        }
538
539        let mut world = World::new();
540
541        let id = world.register_system(increment_sys);
542
543        // Insert the resource after registering the system.
544        world.insert_resource(Counter(1));
545        assert_eq!(*world.resource::<Counter>(), Counter(1));
546
547        let output = world.run_system(id).expect("system runs successfully");
548        assert_eq!(*world.resource::<Counter>(), Counter(2));
549        assert_eq!(output, NonCopy(2));
550
551        let output = world.run_system(id).expect("system runs successfully");
552        assert_eq!(*world.resource::<Counter>(), Counter(3));
553        assert_eq!(output, NonCopy(3));
554    }
555
556    #[test]
557    fn exclusive_system() {
558        let mut world = World::new();
559        let exclusive_system_id = world.register_system(|world: &mut World| {
560            world.spawn_empty();
561        });
562        let entity_count = world.entities.len();
563        let _ = world.run_system(exclusive_system_id);
564        assert_eq!(world.entities.len(), entity_count + 1);
565    }
566
567    #[test]
568    fn nested_systems() {
569        use crate::system::SystemId;
570
571        #[derive(Component)]
572        struct Callback(SystemId);
573
574        fn nested(query: Query<&Callback>, mut commands: Commands) {
575            for callback in query.iter() {
576                commands.run_system(callback.0);
577            }
578        }
579
580        let mut world = World::new();
581        world.insert_resource(Counter(0));
582
583        let increment_two = world.register_system(|mut counter: ResMut<Counter>| {
584            counter.0 += 2;
585        });
586        let increment_three = world.register_system(|mut counter: ResMut<Counter>| {
587            counter.0 += 3;
588        });
589        let nested_id = world.register_system(nested);
590
591        world.spawn(Callback(increment_two));
592        world.spawn(Callback(increment_three));
593        let _ = world.run_system(nested_id);
594        assert_eq!(*world.resource::<Counter>(), Counter(5));
595    }
596
597    #[test]
598    fn nested_systems_with_inputs() {
599        use crate::system::SystemId;
600
601        #[derive(Component)]
602        struct Callback(SystemId<u8>, u8);
603
604        fn nested(query: Query<&Callback>, mut commands: Commands) {
605            for callback in query.iter() {
606                commands.run_system_with_input(callback.0, callback.1);
607            }
608        }
609
610        let mut world = World::new();
611        world.insert_resource(Counter(0));
612
613        let increment_by =
614            world.register_system(|In(amt): In<u8>, mut counter: ResMut<Counter>| {
615                counter.0 += amt;
616            });
617        let nested_id = world.register_system(nested);
618
619        world.spawn(Callback(increment_by, 2));
620        world.spawn(Callback(increment_by, 3));
621        let _ = world.run_system(nested_id);
622        assert_eq!(*world.resource::<Counter>(), Counter(5));
623    }
624}