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}