Skip to main content

unlit_ecs/
lifecycle.rs

1//! Lifecycle behaviour components: callbacks that run when an entity is
2//! spawned or despawned.
3//!
4//! These are components, not hooks into a scheduler: the callbacks live in the
5//! entity's own row and the world runs them at the structural change that
6//! produced or is about to remove it. There is no change detection and no
7//! deferred pass -- an [`OnDespawn`] callback runs while the entity still has
8//! its components, because afterwards there is nothing left to read.
9//!
10//! A callback gets the scope of the entity's own scope, which is its parent, and
11//! cannot change the world's structure: it queues those changes instead. The
12//! callback is taken out of the entity's row for the run and put back after, so
13//! a callback that reaches for its own lifecycle component reads the row rather
14//! than finding it held open by its own run.
15
16use crate::entity::Entity;
17use crate::view::Scope;
18
19/// The callback of a lifecycle component.
20pub type LifecycleCallback = Box<dyn for<'a, 'b> FnMut(&'a Scope<'b>, Entity)>;
21
22/// Declare one lifecycle component: its callback and its constructor.
23macro_rules! lifecycle_component {
24    ($(#[$meta:meta])* $name:ident) => {
25        $(#[$meta])*
26        pub struct $name(
27            /// The callback, run at the structural change.
28            pub LifecycleCallback,
29        );
30
31        impl $name {
32            #[doc = concat!("Wrap `callback` as an [`", stringify!($name), "`].")]
33            #[must_use]
34            pub fn new(
35                callback: impl for<'a, 'b> FnMut(&'a Scope<'b>, Entity) + 'static,
36            ) -> Self {
37                Self(Box::new(callback))
38            }
39        }
40
41        impl $crate::lifecycle::Lifecycle for $name {
42            fn take(&mut self) -> LifecycleCallback {
43                core::mem::replace(&mut self.0, Box::new(|_, _| {}))
44            }
45
46            fn restore(&mut self, callback: LifecycleCallback) {
47                self.0 = callback;
48            }
49        }
50    };
51}
52
53lifecycle_component!(
54    /// The callback that runs right after the entity is spawned.
55    OnSpawn
56);
57lifecycle_component!(
58    /// The callback that runs right before the entity is despawned.
59    OnDespawn
60);
61
62/// A component whose callback the world runs at a structural change.
63///
64/// The trait is what lets the spawn and despawn paths share one runner. It is
65/// sealed: the world only knows how to run [`OnSpawn`] and [`OnDespawn`], so
66/// implementing it elsewhere would not make anything run.
67pub trait Lifecycle: 'static + sealed::Sealed {
68    /// Take the callback out, leaving a placeholder in its place.
69    fn take(&mut self) -> LifecycleCallback;
70
71    /// Put a callback taken by [`Lifecycle::take`] back.
72    fn restore(&mut self, callback: LifecycleCallback);
73}
74
75mod sealed {
76    /// Keeps [`Lifecycle`](super::Lifecycle) to the components the world runs.
77    pub trait Sealed {}
78
79    impl Sealed for super::OnSpawn {}
80    impl Sealed for super::OnDespawn {}
81}