Skip to main content

gizmo_core/world/
resources.rs

1use std::any::TypeId;
2use std::marker::PhantomData;
3use std::sync::{RwLockReadGuard, RwLockWriteGuard};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ResourceFetchError {
7    NotFound(TypeId),
8    BorrowConflict(TypeId),
9}
10
11pub struct ResourceReadGuard<'a, T> {
12    pub(crate) guard: RwLockReadGuard<'a, Box<dyn std::any::Any + Send + Sync>>,
13    pub(crate) _marker: PhantomData<T>,
14}
15
16impl<'a, T: 'static> std::ops::Deref for ResourceReadGuard<'a, T> {
17    type Target = T;
18    fn deref(&self) -> &Self::Target {
19        self.guard.downcast_ref::<T>().unwrap()
20    }
21}
22
23pub struct ResourceWriteGuard<'a, T> {
24    pub(crate) guard: RwLockWriteGuard<'a, Box<dyn std::any::Any + Send + Sync>>,
25    pub(crate) _marker: PhantomData<T>,
26}
27
28impl<'a, T: 'static> std::ops::Deref for ResourceWriteGuard<'a, T> {
29    type Target = T;
30    fn deref(&self) -> &Self::Target {
31        self.guard.downcast_ref::<T>().unwrap()
32    }
33}
34
35impl<'a, T: 'static> std::ops::DerefMut for ResourceWriteGuard<'a, T> {
36    fn deref_mut(&mut self) -> &mut Self::Target {
37        self.guard.downcast_mut::<T>().unwrap()
38    }
39}
40
41/// Sıfır allocation ile yaşayan entity'ler üzerinde iterasyon yapan iterator.
42
43#[cfg(test)]
44mod tests {
45    use crate::impl_component;
46    use crate::Entity;
47    use crate::World;
48
49    // Test component types
50    #[derive(Debug, Clone, PartialEq)]
51    struct Position {
52        x: f32,
53        y: f32,
54    }
55
56    #[derive(Debug, Clone, PartialEq)]
57    struct Health(u32);
58
59    impl_component!(Position, Health);
60
61    #[test]
62    fn test_spawn_and_alive_count() {
63        let mut world = World::new();
64        let e1 = world.spawn();
65        let e2 = world.spawn();
66        let e3 = world.spawn();
67        assert_eq!(world.entity_count(), 3);
68        assert!(world.is_alive(e1));
69        assert!(world.is_alive(e2));
70        assert!(world.is_alive(e3));
71    }
72
73    #[test]
74    fn test_despawn_removes_components() {
75        let mut world = World::new();
76        let e1 = world.spawn();
77        world.add_component(e1, Position { x: 1.0, y: 2.0 });
78        world.add_component(e1, Health(100));
79
80        assert!(world.borrow::<Position>().get(e1.id()).is_some());
81        assert!(world.borrow::<Health>().get(e1.id()).is_some());
82
83        world.despawn(e1);
84
85        assert!(!world.is_alive(e1));
86        assert!(world.borrow::<Position>().get(e1.id()).is_none());
87        assert!(world.borrow::<Health>().get(e1.id()).is_none());
88    }
89
90    #[test]
91    fn test_despawn_only_touches_relevant_storages() {
92        let mut world = World::new();
93        let e1 = world.spawn();
94        let e2 = world.spawn();
95
96        // e1 has Position only, e2 has both
97        world.add_component(e1, Position { x: 0.0, y: 0.0 });
98        world.add_component(e2, Position { x: 1.0, y: 1.0 });
99        world.add_component(e2, Health(50));
100
101        // Despawn e1 — should not affect e2
102        world.despawn(e1);
103
104        assert!(world.borrow::<Position>().get(e2.id()).is_some());
105        assert!(world.borrow::<Health>().get(e2.id()).is_some());
106        assert_eq!(world.entity_count(), 1);
107    }
108
109    #[test]
110    fn test_iter_alive_entities_zero_allocation() {
111        let mut world = World::new();
112        let _e1 = world.spawn();
113        let e2 = world.spawn();
114        let _e3 = world.spawn();
115
116        world.despawn(e2);
117
118        // Iterator should return 2 entities (e1 and e3), skipping e2
119        let alive: Vec<Entity> = world.iter_alive_entities();
120        assert_eq!(alive.len(), 2);
121        assert!(alive.iter().all(|e: &Entity| e.id() != e2.id()));
122    }
123
124    #[test]
125    fn test_entity_id_reuse_after_despawn() {
126        let mut world = World::new();
127        let e1 = world.spawn();
128        let old_id = e1.id();
129        let old_gen = e1.generation();
130
131        world.despawn(e1);
132
133        let e_new = world.spawn();
134        // Should reuse the same ID with bumped generation
135        assert_eq!(e_new.id(), old_id);
136        assert_eq!(e_new.generation(), old_gen + 1);
137
138        // Old entity should not be alive
139        assert!(!world.is_alive(e1));
140        assert!(world.is_alive(e_new));
141    }
142
143    #[test]
144    fn test_add_component_overwrites() {
145        let mut world = World::new();
146        let e = world.spawn();
147        world.add_component(e, Health(100));
148        world.add_component(e, Health(50)); // Overwrite
149
150        let hp = world.borrow::<Health>();
151        assert_eq!(hp.get(e.id()).unwrap().0, 50);
152    }
153
154    /// Aynı component türü iki kez eklenince archetype migration'da veri güncellenmeli.
155    #[test]
156    fn test_double_add_component_despawn_safe() {
157        let mut world = World::new();
158        let e = world.spawn();
159        let id = e.id();
160        world.add_component(e, Health(100));
161        world.add_component(e, Health(50));
162
163        world.despawn(e);
164
165        assert!(!world.is_alive(e));
166        assert!(world.borrow::<Health>().get(id).is_none());
167
168        // ID yeniden kullanıldığında eski Health taşınmamalı
169        let e2 = world.spawn();
170        assert_eq!(e2.id(), id);
171        assert!(world.borrow::<Health>().get(e2.id()).is_none());
172    }
173
174    #[test]
175    fn test_component_registration_metadata() {
176        let mut world = World::new();
177        assert_eq!(world.registered_component_count(), 0);
178        assert!(!world.is_component_registered::<Position>());
179        assert!(!world.is_component_registered::<Health>());
180
181        let e = world.spawn();
182        world.add_component(e, Position { x: 1.0, y: 2.0 });
183        assert!(world.is_component_registered::<Position>());
184        assert_eq!(world.registered_component_count(), 1);
185
186        world.add_component(e, Health(100));
187        assert!(world.is_component_registered::<Health>());
188        assert_eq!(world.registered_component_count(), 2);
189
190        // remove metadata'yi silmez
191        world.remove_component::<Health>(e);
192        assert!(world.is_component_registered::<Health>());
193        assert_eq!(world.registered_component_count(), 2);
194    }
195
196    #[derive(Debug, Clone, PartialEq)]
197    struct HookTracker(u32);
198    impl_component!(HookTracker);
199
200    #[test]
201    fn test_component_hooks() {
202        use std::sync::atomic::{AtomicUsize, Ordering};
203        static ADD_COUNT: AtomicUsize = AtomicUsize::new(0);
204        static SET_COUNT: AtomicUsize = AtomicUsize::new(0);
205        static REMOVE_COUNT: AtomicUsize = AtomicUsize::new(0);
206
207        ADD_COUNT.store(0, Ordering::SeqCst);
208        SET_COUNT.store(0, Ordering::SeqCst);
209        REMOVE_COUNT.store(0, Ordering::SeqCst);
210
211        let mut world = World::new();
212
213        world.register_on_add::<HookTracker>(Box::new(|_, _| {
214            ADD_COUNT.fetch_add(1, Ordering::SeqCst);
215        }));
216        world.register_on_set::<HookTracker>(Box::new(|_, _| {
217            SET_COUNT.fetch_add(1, Ordering::SeqCst);
218        }));
219        world.register_on_remove::<HookTracker>(Box::new(|_, _| {
220            REMOVE_COUNT.fetch_add(1, Ordering::SeqCst);
221        }));
222
223        let e1 = world.spawn();
224
225        // Adding should trigger OnAdd and OnSet
226        world.add_component(e1, HookTracker(1));
227        assert_eq!(ADD_COUNT.load(Ordering::SeqCst), 1);
228        assert_eq!(SET_COUNT.load(Ordering::SeqCst), 1);
229        assert_eq!(REMOVE_COUNT.load(Ordering::SeqCst), 0);
230
231        // Overwriting should trigger ONLY OnSet
232        world.add_component(e1, HookTracker(2));
233        assert_eq!(ADD_COUNT.load(Ordering::SeqCst), 1);
234        assert_eq!(SET_COUNT.load(Ordering::SeqCst), 2);
235        assert_eq!(REMOVE_COUNT.load(Ordering::SeqCst), 0);
236
237        // Removing should trigger OnRemove
238        world.remove_component::<HookTracker>(e1);
239        assert_eq!(ADD_COUNT.load(Ordering::SeqCst), 1);
240        assert_eq!(SET_COUNT.load(Ordering::SeqCst), 2);
241        assert_eq!(REMOVE_COUNT.load(Ordering::SeqCst), 1);
242
243        // Despawn should trigger OnRemove for remaining components
244        world.add_component(e1, HookTracker(3));
245        assert_eq!(ADD_COUNT.load(Ordering::SeqCst), 2); // added again
246        assert_eq!(SET_COUNT.load(Ordering::SeqCst), 3);
247
248        world.despawn(e1);
249        assert_eq!(REMOVE_COUNT.load(Ordering::SeqCst), 2); // removed again via despawn
250    }
251
252    #[test]
253    fn test_world_compaction() {
254        let mut world = World::new();
255
256        // Spawn 100 entities with two components
257        for _ in 0..100 {
258            let e = world.spawn();
259            world.add_component(e, Position { x: 0.0, y: 0.0 });
260            world.add_component(e, Health(10));
261        }
262
263        assert_eq!(world.archetype_index.archetype_count(), 3); // 0 (empty), 1 (Pos), 2 (Pos, Health)
264
265        let all_entities = world.iter_alive_entities();
266
267        // Remove 'Health' from the first 50 entities.
268        // This moves them back to Archetype 1 (Pos).
269        for e in all_entities.iter().take(50) {
270            world.remove_component::<Health>(*e);
271        }
272
273        // Despawn the remaining 50 entities. (Archetype 2 is now completely EMPTY)
274        for e in all_entities.iter().skip(50) {
275            world.despawn(*e);
276        }
277
278        // Wait, removing components moved the 50 entities to archetype index 1.
279        // Despawning the remaining 50 means archetype index 2 has 0 entities.
280        assert_eq!(world.archetype_index.archetypes[2].len(), 0);
281
282        // Call compaction
283        world.compact();
284
285        // The empty archetype 2 should be gone.
286        assert_eq!(world.archetype_index.archetype_count(), 2);
287
288        // The remaining 50 entities should still be fully accessible
289        let pos_view = world.borrow::<Position>();
290        let mut count = 0;
291        for e in world.iter_alive_entities() {
292            let eid: u32 = e.id();
293            assert!(pos_view.get(eid).is_some());
294            count += 1;
295        }
296        assert_eq!(count, 50);
297    }
298}