intuicio_framework_ecs/
commands.rs

1use crate::{
2    bundle::{Bundle, BundleColumns},
3    entity::Entity,
4    world::World,
5};
6use std::{
7    marker::PhantomData,
8    sync::{Arc, Mutex},
9};
10
11pub trait Command: Send + Sync + 'static {
12    fn execute(self, world: &mut World);
13}
14
15#[derive(Default)]
16pub struct CommandBuffer {
17    #[allow(clippy::type_complexity)]
18    commands: Vec<Box<dyn FnOnce(&mut World) + Send + Sync>>,
19}
20
21impl CommandBuffer {
22    pub fn schedule(&mut self, command: impl FnOnce(&mut World) + Send + Sync + 'static) {
23        self.commands.push(Box::new(command));
24    }
25
26    pub fn command(&mut self, command: impl Command) {
27        self.schedule(|world| command.execute(world));
28    }
29
30    pub fn commands(&mut self, mut buffer: CommandBuffer) {
31        self.schedule(move |world| {
32            buffer.execute(world);
33        });
34    }
35
36    pub fn execute(&mut self, world: &mut World) {
37        for command in std::mem::take(&mut self.commands) {
38            (command)(world);
39        }
40    }
41
42    pub fn clear(&mut self) {
43        self.commands.clear();
44    }
45
46    pub fn is_empty(&self) -> bool {
47        self.commands.is_empty()
48    }
49
50    pub fn len(&self) -> usize {
51        self.commands.len()
52    }
53}
54
55#[derive(Default, Clone)]
56pub struct SharedCommandBuffer {
57    inner: Arc<Mutex<CommandBuffer>>,
58}
59
60impl SharedCommandBuffer {
61    pub fn with<R>(&mut self, f: impl FnOnce(&mut CommandBuffer) -> R) -> Option<R> {
62        if let Ok(mut buffer) = self.inner.lock() {
63            Some(f(&mut buffer))
64        } else {
65            None
66        }
67    }
68
69    pub fn try_with<R>(&mut self, f: impl FnOnce(&mut CommandBuffer) -> R) -> Option<R> {
70        if let Ok(mut buffer) = self.inner.try_lock() {
71            Some(f(&mut buffer))
72        } else {
73            None
74        }
75    }
76}
77
78pub struct SpawnCommand<T: Bundle + Send + Sync + 'static> {
79    bundle: T,
80}
81
82impl<T: Bundle + Send + Sync + 'static> SpawnCommand<T> {
83    pub fn new(bundle: T) -> Self {
84        Self { bundle }
85    }
86}
87
88impl<T: Bundle + Send + Sync + 'static> Command for SpawnCommand<T> {
89    fn execute(self, world: &mut World) {
90        world.spawn(self.bundle).unwrap();
91    }
92}
93
94pub struct DespawnCommand {
95    entity: Entity,
96}
97
98impl DespawnCommand {
99    pub fn new(entity: Entity) -> Self {
100        Self { entity }
101    }
102}
103
104impl Command for DespawnCommand {
105    fn execute(self, world: &mut World) {
106        world.despawn(self.entity).unwrap();
107    }
108}
109
110pub struct InsertCommand<T: Bundle + Send + Sync + 'static> {
111    entity: Entity,
112    bundle: T,
113}
114
115impl<T: Bundle + Send + Sync + 'static> InsertCommand<T> {
116    pub fn new(entity: Entity, bundle: T) -> Self {
117        Self { entity, bundle }
118    }
119}
120
121impl<T: Bundle + Send + Sync + 'static> Command for InsertCommand<T> {
122    fn execute(self, world: &mut World) {
123        world.insert(self.entity, self.bundle).unwrap();
124    }
125}
126
127pub struct RemoveCommand<T: BundleColumns> {
128    entity: Entity,
129    _phantom: PhantomData<fn() -> T>,
130}
131
132impl<T: Bundle + Send + Sync + 'static> RemoveCommand<T> {
133    pub fn new(entity: Entity) -> Self {
134        Self {
135            entity,
136            _phantom: PhantomData,
137        }
138    }
139}
140
141impl<T: Bundle + Send + Sync + 'static> Command for RemoveCommand<T> {
142    fn execute(self, world: &mut World) {
143        world.remove::<T>(self.entity).unwrap();
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::world::World;
151
152    #[test]
153    fn test_async() {
154        fn is_async<T: Send + Sync>() {}
155
156        is_async::<CommandBuffer>();
157        is_async::<SharedCommandBuffer>();
158    }
159
160    #[test]
161    fn test_command_buffer() {
162        let mut world = World::default();
163        let mut buffer = CommandBuffer::default();
164        assert!(world.is_empty());
165
166        buffer.command(SpawnCommand::new((1u8, 2u16, 3u32)));
167        buffer.execute(&mut world);
168        assert_eq!(world.len(), 1);
169
170        let entity = world.entities().next().unwrap();
171        buffer.command(DespawnCommand::new(entity));
172        buffer.execute(&mut world);
173        assert!(world.is_empty());
174    }
175}