1use crate::simple_world::{EntityId, SimpleWorld};
2use alloc::vec::Vec;
3use soroban_sdk::{Bytes, Symbol};
4
5pub type OnAddHook = fn(entity_id: EntityId, component_type: &Symbol, data: &Bytes);
7
8pub type OnRemoveHook = fn(entity_id: EntityId, component_type: &Symbol);
10
11pub struct HookRegistry {
29 add_hooks: Vec<(Symbol, OnAddHook)>,
30 remove_hooks: Vec<(Symbol, OnRemoveHook)>,
31}
32
33impl HookRegistry {
34 pub fn new() -> Self {
36 Self {
37 add_hooks: Vec::new(),
38 remove_hooks: Vec::new(),
39 }
40 }
41
42 pub fn on_add(&mut self, component_type: Symbol, hook: OnAddHook) {
44 self.add_hooks.push((component_type, hook));
45 }
46
47 pub fn on_remove(&mut self, component_type: Symbol, hook: OnRemoveHook) {
49 self.remove_hooks.push((component_type, hook));
50 }
51
52 pub fn fire_on_add(&self, entity_id: EntityId, component_type: &Symbol, data: &Bytes) {
54 for (ctype, hook) in &self.add_hooks {
55 if ctype == component_type {
56 hook(entity_id, component_type, data);
57 }
58 }
59 }
60
61 pub fn fire_on_remove(&self, entity_id: EntityId, component_type: &Symbol) {
63 for (ctype, hook) in &self.remove_hooks {
64 if ctype == component_type {
65 hook(entity_id, component_type);
66 }
67 }
68 }
69
70 pub fn add_hook_count(&self) -> usize {
72 self.add_hooks.len()
73 }
74
75 pub fn remove_hook_count(&self) -> usize {
77 self.remove_hooks.len()
78 }
79}
80
81impl Default for HookRegistry {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87pub struct HookedWorld {
109 world: SimpleWorld,
110 hooks: HookRegistry,
111}
112
113impl HookedWorld {
114 pub fn new(world: SimpleWorld) -> Self {
116 Self {
117 world,
118 hooks: HookRegistry::new(),
119 }
120 }
121
122 pub fn with_hooks(world: SimpleWorld, hooks: HookRegistry) -> Self {
124 Self { world, hooks }
125 }
126
127 pub fn world(&self) -> &SimpleWorld {
129 &self.world
130 }
131
132 pub fn world_mut(&mut self) -> &mut SimpleWorld {
134 &mut self.world
135 }
136
137 pub fn hooks(&self) -> &HookRegistry {
139 &self.hooks
140 }
141
142 pub fn hooks_mut(&mut self) -> &mut HookRegistry {
144 &mut self.hooks
145 }
146
147 pub fn into_inner(self) -> SimpleWorld {
149 self.world
150 }
151
152 pub fn spawn_entity(&mut self) -> EntityId {
154 self.world.spawn_entity()
155 }
156
157 pub fn add_component(&mut self, entity_id: EntityId, component_type: Symbol, data: Bytes) {
159 self.world
160 .add_component(entity_id, component_type.clone(), data.clone());
161 self.hooks.fire_on_add(entity_id, &component_type, &data);
162 }
163
164 pub fn remove_component(&mut self, entity_id: EntityId, component_type: &Symbol) -> bool {
166 self.hooks.fire_on_remove(entity_id, component_type);
167 self.world.remove_component(entity_id, component_type)
168 }
169
170 pub fn get_component(&self, entity_id: EntityId, component_type: &Symbol) -> Option<Bytes> {
172 self.world.get_component(entity_id, component_type)
173 }
174
175 pub fn has_component(&self, entity_id: EntityId, component_type: &Symbol) -> bool {
177 self.world.has_component(entity_id, component_type)
178 }
179
180 pub fn despawn_entity(&mut self, entity_id: EntityId) {
182 if let Some(types) = self.world.entity_components.get(entity_id) {
184 for i in 0..types.len() {
185 if let Some(t) = types.get(i) {
186 self.hooks.fire_on_remove(entity_id, &t);
187 }
188 }
189 }
190 self.world.despawn_entity(entity_id);
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use soroban_sdk::{symbol_short, Env};
198
199 fn noop_add_hook(_entity_id: EntityId, _component_type: &Symbol, _data: &Bytes) {}
200 fn noop_remove_hook(_entity_id: EntityId, _component_type: &Symbol) {}
201
202 #[test]
203 fn test_hook_registry_new() {
204 let registry = HookRegistry::new();
205 assert_eq!(registry.add_hook_count(), 0);
206 assert_eq!(registry.remove_hook_count(), 0);
207 }
208
209 #[test]
210 fn test_hook_registry_register() {
211 let mut registry = HookRegistry::new();
212 registry.on_add(symbol_short!("pos"), noop_add_hook);
213 registry.on_remove(symbol_short!("pos"), noop_remove_hook);
214 assert_eq!(registry.add_hook_count(), 1);
215 assert_eq!(registry.remove_hook_count(), 1);
216 }
217
218 #[test]
219 fn test_hook_registry_multiple_hooks() {
220 let mut registry = HookRegistry::new();
221 registry.on_add(symbol_short!("pos"), noop_add_hook);
222 registry.on_add(symbol_short!("vel"), noop_add_hook);
223 registry.on_remove(symbol_short!("pos"), noop_remove_hook);
224 assert_eq!(registry.add_hook_count(), 2);
225 assert_eq!(registry.remove_hook_count(), 1);
226 }
227
228 #[test]
229 fn test_hooked_world_add_component() {
230 let env = Env::default();
231 let world = SimpleWorld::new(&env);
232 let mut hooked = HookedWorld::new(world);
233 hooked
234 .hooks_mut()
235 .on_add(symbol_short!("pos"), noop_add_hook);
236
237 let e1 = hooked.spawn_entity();
238 let data = Bytes::from_array(&env, &[1, 2, 3, 4]);
239 hooked.add_component(e1, symbol_short!("pos"), data.clone());
240
241 assert!(hooked.has_component(e1, &symbol_short!("pos")));
243 assert_eq!(hooked.get_component(e1, &symbol_short!("pos")), Some(data));
244 }
245
246 #[test]
247 fn test_hooked_world_remove_component() {
248 let env = Env::default();
249 let world = SimpleWorld::new(&env);
250 let mut hooked = HookedWorld::new(world);
251 hooked
252 .hooks_mut()
253 .on_remove(symbol_short!("pos"), noop_remove_hook);
254
255 let e1 = hooked.spawn_entity();
256 let data = Bytes::from_array(&env, &[1, 2, 3, 4]);
257 hooked.add_component(e1, symbol_short!("pos"), data);
258
259 assert!(hooked.remove_component(e1, &symbol_short!("pos")));
260 assert!(!hooked.has_component(e1, &symbol_short!("pos")));
261 }
262
263 #[test]
264 fn test_hooked_world_despawn() {
265 let env = Env::default();
266 let world = SimpleWorld::new(&env);
267 let mut hooked = HookedWorld::new(world);
268 hooked
269 .hooks_mut()
270 .on_remove(symbol_short!("a"), noop_remove_hook);
271
272 let e1 = hooked.spawn_entity();
273 let data = Bytes::from_array(&env, &[1]);
274 hooked.add_component(e1, symbol_short!("a"), data.clone());
275 hooked.add_component(e1, symbol_short!("b"), data);
276
277 hooked.despawn_entity(e1);
278
279 assert!(!hooked.has_component(e1, &symbol_short!("a")));
280 assert!(!hooked.has_component(e1, &symbol_short!("b")));
281 }
282
283 #[test]
284 fn test_hooked_world_into_inner() {
285 let env = Env::default();
286 let world = SimpleWorld::new(&env);
287 let mut hooked = HookedWorld::new(world);
288
289 let e1 = hooked.spawn_entity();
290 let data = Bytes::from_array(&env, &[1]);
291 hooked.add_component(e1, symbol_short!("test"), data);
292
293 let inner = hooked.into_inner();
294 assert!(inner.has_component(e1, &symbol_short!("test")));
295 }
296
297 #[test]
298 fn test_hooked_world_with_hooks() {
299 let env = Env::default();
300 let world = SimpleWorld::new(&env);
301
302 let mut hooks = HookRegistry::new();
303 hooks.on_add(symbol_short!("pos"), noop_add_hook);
304
305 let hooked = HookedWorld::with_hooks(world, hooks);
306 assert_eq!(hooked.hooks().add_hook_count(), 1);
307 }
308}