intuicio_framework_ecs/
observer.rs1use crate::{commands::CommandBuffer, entity::Entity, world::World, Component};
2use intuicio_data::type_hash::TypeHash;
3use std::collections::HashMap;
4
5#[derive(Default)]
6pub struct ChangeObserver {
7 pub commands: CommandBuffer,
8 #[allow(clippy::type_complexity)]
9 on_added:
10 HashMap<TypeHash, Vec<Box<dyn FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync>>>,
11 #[allow(clippy::type_complexity)]
12 on_removed:
13 HashMap<TypeHash, Vec<Box<dyn FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync>>>,
14 #[allow(clippy::type_complexity)]
15 on_updated:
16 HashMap<TypeHash, Vec<Box<dyn FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync>>>,
17}
18
19impl ChangeObserver {
20 pub fn on_added<T: Component>(
21 &mut self,
22 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
23 ) {
24 self.on_added_raw(TypeHash::of::<T>(), callback);
25 }
26
27 pub fn on_added_raw(
28 &mut self,
29 type_hash: TypeHash,
30 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
31 ) {
32 self.on_added
33 .entry(type_hash)
34 .or_default()
35 .push(Box::new(callback));
36 }
37
38 pub fn on_removed<T: Component>(
39 &mut self,
40 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
41 ) {
42 self.on_removed_raw(TypeHash::of::<T>(), callback);
43 }
44
45 pub fn on_removed_raw(
46 &mut self,
47 type_hash: TypeHash,
48 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
49 ) {
50 self.on_removed
51 .entry(type_hash)
52 .or_default()
53 .push(Box::new(callback));
54 }
55
56 pub fn on_updated<T: Component>(
57 &mut self,
58 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
59 ) {
60 self.on_updated_raw(TypeHash::of::<T>(), callback);
61 }
62
63 pub fn on_updated_raw(
64 &mut self,
65 type_hash: TypeHash,
66 callback: impl FnMut(&World, &mut CommandBuffer, Entity) + Send + Sync + 'static,
67 ) {
68 self.on_updated
69 .entry(type_hash)
70 .or_default()
71 .push(Box::new(callback));
72 }
73
74 pub fn process(&mut self, world: &mut World) {
75 for (entity, types) in world.added().iter() {
76 for type_hash in types {
77 if let Some(listeners) = self.on_added.get_mut(type_hash) {
78 for listener in listeners {
79 listener(world, &mut self.commands, entity);
80 }
81 }
82 }
83 }
84 if let Some(updated) = world.updated() {
85 for (entity, types) in updated.iter() {
86 for type_hash in types {
87 if let Some(listeners) = self.on_updated.get_mut(type_hash) {
88 for listener in listeners {
89 listener(world, &mut self.commands, entity);
90 }
91 }
92 }
93 }
94 }
95 for (entity, types) in world.removed().iter() {
96 for type_hash in types {
97 if let Some(listeners) = self.on_removed.get_mut(type_hash) {
98 for listener in listeners {
99 listener(world, &mut self.commands, entity);
100 }
101 }
102 }
103 }
104 }
105
106 pub fn process_execute(&mut self, world: &mut World) {
107 self.process(world);
108 self.commands.execute(world);
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use crate::{commands::DespawnCommand, world::World};
116 use std::sync::{Arc, RwLock};
117
118 #[test]
119 fn test_async() {
120 fn is_async<T: Send + Sync>() {}
121
122 is_async::<ChangeObserver>();
123 }
124
125 #[test]
126 fn test_change_observer() {
127 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
128 enum Phase {
129 None,
130 Added,
131 Updated,
132 Removed,
133 }
134
135 let phase = Arc::new(RwLock::new(Phase::None));
136 let phase1 = phase.clone();
137 let phase2 = phase.clone();
138 let phase3 = phase.clone();
139
140 let mut observer = ChangeObserver::default();
141 observer.on_added::<bool>(move |_, commands, entity| {
142 let phase1 = phase1.clone();
143 commands.schedule(move |world| {
146 let mut access = world.get::<true, bool>(entity, true).unwrap();
147 let data = access.write().unwrap();
148 *data = !*data;
149 world.update::<bool>(entity);
150 *phase1.write().unwrap() = Phase::Added;
151 });
152 });
153 observer.on_updated::<bool>(move |_, commands, entity| {
154 commands.command(DespawnCommand::new(entity));
155 *phase2.write().unwrap() = Phase::Updated;
156 });
157 observer.on_removed::<bool>(move |_, _, _| {
158 *phase3.write().unwrap() = Phase::Removed;
159 });
160
161 let mut world = World::default();
162 let entity = world.spawn((false,)).unwrap();
163 assert!(!*world
164 .get::<true, bool>(entity, false)
165 .unwrap()
166 .read()
167 .unwrap());
168 assert_eq!(*phase.read().unwrap(), Phase::None);
169
170 observer.process(&mut world);
171 world.clear_changes();
172 observer.commands.execute(&mut world);
173 assert_eq!(*phase.read().unwrap(), Phase::Added);
174
175 observer.process(&mut world);
176 world.clear_changes();
177 observer.commands.execute(&mut world);
178 assert_eq!(*phase.read().unwrap(), Phase::Updated);
179
180 observer.process(&mut world);
181 world.clear_changes();
182 observer.commands.execute(&mut world);
183 assert_eq!(*phase.read().unwrap(), Phase::Removed);
184 }
185}