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}