hecs/
command_buffer.rs

1use core::any::TypeId;
2use core::mem;
3use core::ops::Range;
4use core::ptr::{self, NonNull};
5
6use crate::alloc::alloc::{alloc, dealloc, Layout};
7use crate::alloc::vec::Vec;
8use crate::archetype::TypeInfo;
9use crate::{align, DynamicBundle};
10use crate::{Bundle, Entity};
11use crate::{Component, World};
12
13/// Records operations for future application to a [`World`]
14///
15/// Useful when operations cannot be applied directly due to ordering concerns or borrow checking.
16///
17/// ```
18/// # use hecs::*;
19/// let mut world = World::new();
20/// let entity = world.reserve_entity();
21/// let mut cmd = CommandBuffer::new();
22/// cmd.insert(entity, (true, 42));
23/// cmd.run_on(&mut world); // cmd can now be reused
24/// assert_eq!(*world.get::<&i32>(entity).unwrap(), 42);
25/// ```
26pub struct CommandBuffer {
27    cmds: Vec<Cmd>,
28    storage: NonNull<u8>,
29    layout: Layout,
30    cursor: usize,
31    components: Vec<ComponentInfo>,
32    ids: Vec<TypeId>,
33}
34
35impl CommandBuffer {
36    /// Create an empty command buffer
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    unsafe fn grow(
42        min_size: usize,
43        cursor: usize,
44        align: usize,
45        storage: NonNull<u8>,
46    ) -> (NonNull<u8>, Layout) {
47        let layout = Layout::from_size_align(min_size.next_power_of_two().max(64), align).unwrap();
48        let new_storage = NonNull::new_unchecked(alloc(layout));
49        ptr::copy_nonoverlapping(storage.as_ptr(), new_storage.as_ptr(), cursor);
50        (new_storage, layout)
51    }
52
53    unsafe fn add_inner(&mut self, ptr: *mut u8, ty: TypeInfo) {
54        let offset = align(self.cursor, ty.layout().align());
55        let end = offset + ty.layout().size();
56
57        if end > self.layout.size() || ty.layout().align() > self.layout.align() {
58            let new_align = self.layout.align().max(ty.layout().align());
59            let (new_storage, new_layout) = Self::grow(end, self.cursor, new_align, self.storage);
60            if self.layout.size() != 0 {
61                dealloc(self.storage.as_ptr(), self.layout);
62            }
63            self.storage = new_storage;
64            self.layout = new_layout;
65        }
66
67        let addr = self.storage.as_ptr().add(offset);
68        ptr::copy_nonoverlapping(ptr, addr, ty.layout().size());
69        self.components.push(ComponentInfo { ty, offset });
70        self.cursor = end;
71    }
72
73    /// Add components from `bundle` to `entity`, if it exists
74    ///
75    /// Pairs well with [`World::reserve_entity`] to spawn entities with a known handle.
76    ///
77    /// When inserting a single component, see [`insert_one`](Self::insert_one) for convenience.
78    pub fn insert(&mut self, entity: Entity, components: impl DynamicBundle) {
79        let first_component = self.components.len();
80        unsafe {
81            components.put(|ptr, ty| self.add_inner(ptr, ty));
82        }
83        self.components[first_component..].sort_unstable_by_key(|c| c.ty);
84        self.cmds.push(Cmd::SpawnOrInsert(EntityIndex {
85            entity: Some(entity),
86            components: first_component..self.components.len(),
87        }));
88    }
89
90    /// Add `component` to `entity`, if the entity exists
91    ///
92    /// See [`insert`](Self::insert).
93    pub fn insert_one(&mut self, entity: Entity, component: impl Component) {
94        self.insert(entity, (component,));
95    }
96
97    /// Remove components from `entity` if they exist
98    ///
99    /// When removing a single component, see [`remove_one`](Self::remove_one) for convenience.
100    pub fn remove<T: Bundle + 'static>(&mut self, ent: Entity) {
101        fn remove_bundle_and_ignore_result<T: Bundle + 'static>(world: &mut World, ents: Entity) {
102            let _ = world.remove::<T>(ents);
103        }
104        self.cmds.push(Cmd::Remove(RemovedComps {
105            remove: remove_bundle_and_ignore_result::<T>,
106            entity: ent,
107        }));
108    }
109
110    /// Remove a component from `entity` if it exists
111    ///
112    /// See [`remove`](Self::remove).
113    pub fn remove_one<T: Component>(&mut self, ent: Entity) {
114        self.remove::<(T,)>(ent);
115    }
116
117    /// Despawn `entity` from World
118    pub fn despawn(&mut self, entity: Entity) {
119        self.cmds.push(Cmd::Despawn(entity));
120    }
121
122    /// Spawn a new entity with `components`
123    ///
124    /// If the [`Entity`] is needed immediately, consider combining [`World::reserve_entity`] with
125    /// [`insert`](CommandBuffer::insert) instead.
126    pub fn spawn(&mut self, components: impl DynamicBundle) {
127        let first_component = self.components.len();
128        unsafe {
129            components.put(|ptr, ty| self.add_inner(ptr, ty));
130        }
131        self.components[first_component..].sort_unstable_by_key(|c| c.ty);
132        self.cmds.push(Cmd::SpawnOrInsert(EntityIndex {
133            entity: None,
134            components: first_component..self.components.len(),
135        }));
136    }
137
138    /// Run recorded commands on `world`, clearing the command buffer
139    pub fn run_on(&mut self, world: &mut World) {
140        for i in 0..self.cmds.len() {
141            match mem::replace(&mut self.cmds[i], Cmd::Despawn(Entity::DANGLING)) {
142                Cmd::SpawnOrInsert(entity) => {
143                    let components = self.build(entity.components);
144                    match entity.entity {
145                        Some(entity) => {
146                            // If `entity` no longer exists, quietly drop the components.
147                            let _ = world.insert(entity, components);
148                        }
149                        None => {
150                            world.spawn(components);
151                        }
152                    }
153                }
154                Cmd::Remove(remove) => {
155                    (remove.remove)(world, remove.entity);
156                }
157                Cmd::Despawn(entity) => {
158                    let _ = world.despawn(entity);
159                }
160            }
161        }
162        // Wipe out component references so `clear` doesn't try to double-free
163        self.components.clear();
164
165        self.clear();
166    }
167
168    fn build(&mut self, components: Range<usize>) -> RecordedEntity<'_> {
169        self.ids.clear();
170        self.ids.extend(
171            self.components[components.clone()]
172                .iter()
173                .map(|x| x.ty.id()),
174        );
175        RecordedEntity {
176            cmd: self,
177            components,
178        }
179    }
180
181    /// Drop all recorded commands
182    pub fn clear(&mut self) {
183        self.ids.clear();
184        self.cursor = 0;
185        for info in self.components.drain(..) {
186            unsafe {
187                info.ty.drop(self.storage.as_ptr().add(info.offset));
188            }
189        }
190        self.cmds.clear();
191    }
192}
193
194unsafe impl Send for CommandBuffer {}
195unsafe impl Sync for CommandBuffer {}
196
197impl Drop for CommandBuffer {
198    fn drop(&mut self) {
199        self.clear();
200        if self.layout.size() != 0 {
201            unsafe {
202                dealloc(self.storage.as_ptr(), self.layout);
203            }
204        }
205    }
206}
207
208impl Default for CommandBuffer {
209    /// Create an empty buffer
210    fn default() -> Self {
211        Self {
212            cmds: Vec::new(),
213            storage: NonNull::dangling(),
214            layout: Layout::from_size_align(0, 8).unwrap(),
215            cursor: 0,
216            components: Vec::new(),
217            ids: Vec::new(),
218        }
219    }
220}
221
222/// The output of an '[CommandBuffer]` suitable for passing to
223/// [`World::spawn_into`](crate::World::spawn_into)
224struct RecordedEntity<'a> {
225    cmd: &'a mut CommandBuffer,
226    components: Range<usize>,
227}
228
229unsafe impl DynamicBundle for RecordedEntity<'_> {
230    fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T {
231        f(&self.cmd.ids)
232    }
233
234    fn type_info(&self) -> Vec<TypeInfo> {
235        self.cmd.components[self.components.clone()]
236            .iter()
237            .map(|x| x.ty)
238            .collect()
239    }
240
241    unsafe fn put(mut self, mut f: impl FnMut(*mut u8, TypeInfo)) {
242        // Zero out the components slice so `drop` won't double-free
243        let components = mem::replace(&mut self.components, 0..0);
244        for info in &self.cmd.components[components] {
245            let ptr = self.cmd.storage.as_ptr().add(info.offset);
246            f(ptr, info.ty);
247        }
248    }
249}
250
251impl Drop for RecordedEntity<'_> {
252    fn drop(&mut self) {
253        // If `put` was never called, we still need to drop this entity's components and discard
254        // their info.
255        unsafe {
256            for info in &self.cmd.components[self.components.clone()] {
257                info.ty.drop(self.cmd.storage.as_ptr().add(info.offset));
258            }
259        }
260    }
261}
262
263/// Data required to store components and their offset
264struct ComponentInfo {
265    ty: TypeInfo,
266    // Position in 'storage'
267    offset: usize,
268}
269
270/// Data of buffered 'entity' and its relative position in component data
271struct EntityIndex {
272    entity: Option<Entity>,
273    // Position of this entity's components in `CommandBuffer::info`
274    //
275    // We could store a single start point for the first initialized entity, rather than one for
276    // each, but this would be more error prone for marginal space savings.
277    components: Range<usize>,
278}
279
280/// Data required to remove components from 'entity'
281struct RemovedComps {
282    remove: fn(&mut World, Entity),
283    entity: Entity,
284}
285
286/// A buffered command
287enum Cmd {
288    SpawnOrInsert(EntityIndex),
289    Remove(RemovedComps),
290    Despawn(Entity),
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn populate_archetypes() {
299        let mut world = World::new();
300        let mut buffer = CommandBuffer::new();
301        let ent = world.reserve_entity();
302        let enta = world.reserve_entity();
303        let entb = world.reserve_entity();
304        let entc = world.reserve_entity();
305        buffer.insert(ent, (true, "a"));
306        buffer.insert(entc, (true, "a"));
307        buffer.insert(enta, (1, 1.0));
308        buffer.insert(entb, (1.0, "a"));
309        buffer.run_on(&mut world);
310        assert_eq!(world.archetypes().len(), 4);
311    }
312
313    #[test]
314    fn failed_insert_regression() {
315        // Verify that failing to insert components doesn't lead to concatenating components
316        // together
317        #[derive(Clone)]
318        struct A;
319
320        let mut world = World::new();
321
322        // Get two IDs
323        let a = world.spawn((A,));
324        let b = world.spawn((A,));
325
326        // Invalidate them both
327        world.clear();
328
329        let mut cmd = CommandBuffer::new();
330        cmd.insert_one(a, A);
331        cmd.insert_one(b, A);
332
333        // Make `a` valid again
334        world.spawn_at(a, ());
335
336        // The insert to `a` should succeed
337        cmd.run_on(&mut world);
338
339        assert!(world.satisfies::<&A>(a));
340    }
341
342    #[test]
343    fn insert_then_remove() {
344        let mut world = World::new();
345        let a = world.spawn(());
346        let mut cmd = CommandBuffer::new();
347        cmd.insert_one(a, 42i32);
348        cmd.remove_one::<i32>(a);
349        cmd.run_on(&mut world);
350        assert!(!world.satisfies::<&i32>(a));
351    }
352
353    #[test]
354    fn remove_then_insert() {
355        let mut world = World::new();
356        let a = world.spawn((17i32,));
357        let mut cmd = CommandBuffer::new();
358        cmd.remove_one::<i32>(a);
359        cmd.insert_one(a, 42i32);
360        cmd.run_on(&mut world);
361        assert_eq!(*world.get::<&i32>(a).unwrap(), 42);
362    }
363}