intuicio_framework_ecs/
commands.rs1use 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}