Skip to main content

unlit_ecs/
command.rs

1//! Deferred structural changes.
2//!
3//! A callback that only has a shared world cannot spawn or despawn directly --
4//! those need `&mut World`. Instead it queues the change on a [`Commands`]
5//! queue; the driver applies the queue with [`World::apply`] once the callback
6//! has returned.
7//!
8//! A queue belongs to a scope: everything queued through it lands in that
9//! scope, and every command carries the scope it was queued from so that
10//! applying it later cannot be redirected by a queue that outlived its scope.
11//!
12//! A queued spawn still needs a handle right away, so [`Commands::spawn`]
13//! reserves one with [`World::reserve_entity`] and returns it. The handle is
14//! usable immediately -- it can be stored in a component or handed to another
15//! callback -- and refers to a live entity once the queue is applied.
16//!
17//! Commands run in the order they were queued. A command that acts on a
18//! reserved entity must therefore be queued after the `Spawn` that brings it
19//! to life: for example [`Commands::despawn`] does nothing when the entity is
20//! not alive yet.
21//!
22//! A queue only queues changes to what its scope owns, and a despawn it cannot
23//! take is dropped when it is queued rather than left to fail quietly when it
24//! is applied. What a command does not decide up front is where the entity has
25//! moved by the time it runs: a queued despawn re-checks its scope when it is
26//! applied, so an entity that has since left that scope survives. A `Spawn`
27//! pushed by hand with [`Commands::push`] carries its own ordering.
28//!
29//! `````rust
30//! # use unlit_ecs::{World, bundle};
31//! let mut world = World::new();
32//!
33//! let entity = {
34//!     let scope = world.root_scope();
35//!     let commands = scope.queue();
36//!     commands.spawn(bundle!["entity"])
37//! };
38//! // Nothing has happened yet.
39//! assert!(!world.root_scope().contains(entity));
40//! world.apply().unwrap();
41//! assert!(world.root_scope().contains(entity));
42//! `````
43
44use crate::bundle::Bundle;
45use crate::entity::Entity;
46use crate::error::EntityAccessError;
47use crate::world::World;
48
49/// A structural change that can be applied later.
50pub trait Command: 'static {
51    /// Apply the change to the world.
52    ///
53    /// Most commands cannot fail: they check what they are allowed to touch
54    /// when they are queued and again when they are applied, and a change that
55    /// no longer applies is dropped. Only a command that spawns on a reserved
56    /// handle has something to report, so it answers the error instead of
57    /// panicking on it.
58    fn apply(self: Box<Self>, world: &mut World) -> Result<(), EntityAccessError>;
59}
60
61/// Erases a command into the world's command storage.
62pub trait CommandErase: Command {
63    /// Erase the command.
64    fn erase(self) -> Box<dyn Command>;
65}
66
67impl<C: Command> CommandErase for C {
68    fn erase(self) -> Box<dyn Command> {
69        Box::new(self)
70    }
71}
72
73/// A queue of structural changes, all of them in one scope.
74///
75/// The queue lives in the world, so deferring a spawn from a callback to the
76/// driver needs no channel.
77#[derive(Clone, Copy)]
78pub struct Commands<'w> {
79    world: &'w World,
80    scope: Entity,
81}
82
83impl core::fmt::Debug for Commands<'_> {
84    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
85        f.debug_struct("Commands")
86            .field("scope", &self.scope)
87            .finish_non_exhaustive()
88    }
89}
90
91impl<'w> Commands<'w> {
92    pub(crate) fn new(world: &'w World, scope: Entity) -> Self {
93        Self { world, scope }
94    }
95
96    /// The scope every command of this queue lands in.
97    #[must_use]
98    pub fn scope(&self) -> Entity {
99        self.scope
100    }
101
102    /// Queue a spawn of `bundle` in this scope and return its handle.
103    ///
104    /// The handle is reserved now and refers to a live entity once the queue is
105    /// applied. Its name comes from the bundle: a type of the caller's own names
106    /// it after itself, and a `bundle!` after the source position that wrote the
107    /// bundle, so a queued spawn names the code that wrote the bundle rather
108    /// than the place that queued it.
109    pub fn spawn<B: Bundle + 'static>(&self, bundle: B) -> Entity {
110        let entity = self.world.reserve_entity(bundle.name());
111        let bundle = bundle.into_bundle();
112        self.push(Spawn {
113            entity,
114            scope: self.scope,
115            bundle: Some(bundle),
116        });
117        entity
118    }
119
120    /// Queue a spawn of `bundle` in this scope under `name`.
121    ///
122    /// [`Commands::spawn`] names the entity after the bundle itself; this is for
123    /// the objects whose name has to say more than that does.
124    pub fn spawn_named<B: Bundle + 'static>(&self, name: &'static str, bundle: B) -> Entity {
125        let entity = self.world.reserve_entity(name);
126        self.push(Spawn {
127            entity,
128            scope: self.scope,
129            bundle: Some(bundle),
130        });
131        entity
132    }
133
134    /// Queue the despawn of `entity` and its subtree.
135    ///
136    /// A refusal is answered when the queue is applied, not when it is
137    /// filled: the queue is filled from a callback that holds a shared world
138    /// and has nobody to answer, and the scope it was filled from is the only
139    /// thing it can say about ownership. [`World::apply`] reports the first
140    /// refusal it meets and keeps going.
141    pub fn despawn(&self, entity: Entity) {
142        self.push(Despawn {
143            entity,
144            scope: self.scope,
145        });
146    }
147
148    /// Queue a relink of `child` into `parent`.
149    ///
150    /// The refusal is answered when the queue is applied: both ends have to
151    /// be the queueing scope's to move, nobody moves a root or the shared
152    /// scope itself, and no relink may hang a scope under its own child.
153    /// [`World::apply`] reports the first refusal it meets and keeps going.
154    pub fn link(&self, child: Entity, parent: Entity) {
155        self.push(Link {
156            child,
157            parent,
158            scope: self.scope,
159        });
160    }
161
162    /// Queue a detach of `child` into this queue's scope.
163    ///
164    /// The refusal is answered when the queue is applied: an entity in the
165    /// shared subtree moves only for the root, and the queueing scope may
166    /// only detach what it owns. Detaching what is already here is the thing
167    /// asked for, so it is answered with `Ok(())`.
168    pub fn unlink(&self, child: Entity) {
169        self.push(Unlink {
170            child,
171            scope: self.scope,
172        });
173    }
174
175    /// Queue a command.
176    pub fn push<C: Command + CommandErase>(&self, command: C) {
177        self.world.push_command(command.erase());
178    }
179}
180
181/// Spawns a bundle on a reserved entity, in a scope.
182pub struct Spawn<B> {
183    /// The reserved handle.
184    pub entity: Entity,
185    /// The scope the entity lands in.
186    pub scope: Entity,
187    bundle: Option<B>,
188}
189
190impl<B: Bundle + 'static> Command for Spawn<B> {
191    fn apply(mut self: Box<Self>, world: &mut World) -> Result<(), EntityAccessError> {
192        let bundle = self.bundle.take().expect("a command is applied only once");
193        world.spawn_at_in(self.entity, self.scope, bundle)
194    }
195}
196
197/// Despawns an entity and its subtree.
198pub struct Despawn {
199    /// The entity.
200    pub entity: Entity,
201    /// The scope the entity lived in when the command was queued.
202    pub scope: Entity,
203}
204
205impl Command for Despawn {
206    fn apply(self: Box<Self>, world: &mut World) -> Result<(), EntityAccessError> {
207        // The scope is what the queue saw at queue time; an entity that has
208        // moved out of it since, or that turned out to be scaffolding, is not
209        // this command's to remove.
210        world.despawn_in(self.entity, self.scope)?;
211        Ok(())
212    }
213}
214
215/// Relinks an entity into another parent.
216pub struct Link {
217    /// The entity to move.
218    pub child: Entity,
219    /// Its new parent.
220    pub parent: Entity,
221    /// The scope the command was queued from.
222    pub scope: Entity,
223}
224
225impl Command for Link {
226    fn apply(self: Box<Self>, world: &mut World) -> Result<(), EntityAccessError> {
227        world.link_in(self.child, self.parent, self.scope)
228    }
229}
230
231/// Detaches an entity into a scope.
232pub struct Unlink {
233    /// The entity to move.
234    pub child: Entity,
235    /// The scope it moves into.
236    pub scope: Entity,
237}
238
239impl Command for Unlink {
240    fn apply(self: Box<Self>, world: &mut World) -> Result<(), EntityAccessError> {
241        world.unlink_in(self.child, self.scope, self.scope)
242    }
243}