ixa/entity/
entity_store.rs

1/*!
2
3The `EntityStore` maintains all registered entities in the form of [`EntityRecord`]s,
4`EntityRecord`s track the count of the instances of the [`Entity`] (valid [`EntityId<Entity>`]
5values) and owns the [`PropertyStore<E>`], which manages the entity's properties.
6
7Although each Entity type may own its own data, client code cannot create or destructure
8`EntityId<Entity>` values directly. Instead, `EntityStore` centrally manages entity counts
9for all registered types so that only valid (existing) `EntityId<E>` values are ever created.
10
11
12*/
13
14use std::any::{Any, TypeId};
15use std::cell::OnceCell;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::sync::{LazyLock, Mutex, OnceLock};
18
19use crate::entity::property_store::PropertyStore;
20use crate::entity::{Entity, EntityId, PopulationIterator};
21use crate::HashMap;
22
23/// Global entity index counter; keeps track of the index that will be assigned to the next entity that
24/// requests an index. Equivalently, holds a *count* of the number of entities currently registered.
25static NEXT_ENTITY_INDEX: Mutex<usize> = Mutex::new(0);
26
27/// For each entity we keep track of the properties associated with it. This maps
28/// `entity_type_id` to `(vec_of_all_property_type_ids, vec_of_required_property_type_ids)`.
29/// This data is actually written by the property ctors with a call to
30/// [`register_property_with_entity()`].
31#[allow(clippy::type_complexity)]
32static ENTITY_METADATA_BUILDER: LazyLock<Mutex<HashMap<TypeId, (Vec<TypeId>, Vec<TypeId>)>>> =
33    LazyLock::new(|| Mutex::new(HashMap::default()));
34
35/// The frozen entity->property registry, created exactly once on first read.
36///
37/// This is derived from `ENTITY_METADATA_BUILDER` by moving the builder `HashMap` out and
38/// converting the `Vec`s to boxed slices to prevent further mutation.
39#[allow(clippy::type_complexity)]
40static ENTITY_METADATA: OnceLock<HashMap<TypeId, (Box<[TypeId]>, Box<[TypeId]>)>> = OnceLock::new();
41
42/// Private helper to fetch or initialize the frozen metadata.
43#[allow(clippy::type_complexity)]
44fn entity_metadata() -> &'static HashMap<TypeId, (Box<[TypeId]>, Box<[TypeId]>)> {
45    ENTITY_METADATA.get_or_init(|| {
46        let mut builder = ENTITY_METADATA_BUILDER.lock().unwrap();
47        let builder = std::mem::take(&mut *builder);
48        builder
49            .into_iter()
50            .map(|(entity_type_id, (props, reqs))| {
51                (
52                    entity_type_id,
53                    (props.into_boxed_slice(), reqs.into_boxed_slice()),
54                )
55            })
56            .collect()
57    })
58}
59
60/// The public setter interface to `ENTITY_METADATA`.
61pub fn register_property_with_entity(
62    entity_type_id: TypeId,
63    property_type_id: TypeId,
64    required: bool,
65) {
66    let mut builder = ENTITY_METADATA_BUILDER.lock().unwrap();
67    if ENTITY_METADATA.get().is_some() {
68        panic!(
69            "`register_property_with_entity()` called after entity metadata was frozen; registration must occur during startup/ctors."
70        );
71    }
72
73    let (property_type_ids, required_property_type_ids) = builder
74        .entry(entity_type_id)
75        .or_insert_with(|| (Vec::new(), Vec::new()));
76    property_type_ids.push(property_type_id);
77    if required {
78        required_property_type_ids.push(property_type_id);
79    }
80}
81
82/// Returns the pre-computed, frozen metadata for an entity type.
83///
84/// This registry is built during startup by property ctors calling
85/// [`register_property_with_entity()`], then frozen exactly once on first read.
86#[must_use]
87pub fn get_entity_metadata_static(
88    entity_type_id: TypeId,
89) -> (&'static [TypeId], &'static [TypeId]) {
90    match entity_metadata().get(&entity_type_id) {
91        Some((props, reqs)) => (props.as_ref(), reqs.as_ref()),
92        None => (&[], &[]),
93    }
94}
95
96/// Adds a new entity to the registry. The job of this method is to create whatever
97/// "singleton" data/metadata is associated with the [`Entity`] if it doesn't already
98/// exist, which in this case is only the value of `Entity::id()`.
99///
100/// In our use case, this method is called in the `ctor` function of each `Entity`
101/// type and ultimately exists only so that we know how many `EntityRecord`s to
102/// construct in the constructor of `EntityStore`, so that we never have to mutate
103/// `EntityStore` itself when an `Entity` is accessed for the first time. (The
104/// `OnceCell`s handle the interior mutability required for initialization.)
105pub fn add_to_entity_registry<R: Entity>() {
106    let _ = R::id();
107}
108
109/// A convenience getter for `NEXT_ENTITY_INDEX`.
110pub fn get_registered_entity_count() -> usize {
111    *NEXT_ENTITY_INDEX.lock().unwrap()
112}
113
114/// Encapsulates the synchronization logic for initializing an entity's index.
115///
116/// Acquires a global lock on the next available item index, but only increments
117/// it if we successfully initialize the provided index. The `index` of a registered
118/// item is assigned at runtime but only once per type. It's possible for a single
119/// type to attempt to initialize its index multiple times from different threads,
120/// which is why all this synchronization is required. However, the overhead
121/// is negligible, as this initialization only happens once upon first access.
122///
123/// In fact, for our use case we know we are calling this function
124/// once for each type in each `Entity`'s `ctor` function, which
125/// should be the only time this method is ever called for the type.
126pub fn initialize_entity_index(plugin_index: &AtomicUsize) -> usize {
127    // Acquire a global lock.
128    let mut guard = NEXT_ENTITY_INDEX.lock().unwrap();
129    let candidate = *guard;
130
131    // Try to claim the candidate index. Here we guard against the potential race condition that
132    // another instance of this plugin in another thread just initialized the index prior to us
133    // obtaining the lock. If the index has been initialized beneath us, we do not update
134    // [`NEXT_ITEM_INDEX`], we just return the value `plugin_index` was initialized to.
135    // For a justification of the data ordering, see:
136    //     https://github.com/CDCgov/ixa/pull/477#discussion_r2244302872
137    match plugin_index.compare_exchange(usize::MAX, candidate, Ordering::AcqRel, Ordering::Acquire)
138    {
139        Ok(_) => {
140            // We won the race — increment the global next plugin index and return the new index
141            *guard += 1;
142            candidate
143        }
144        Err(existing) => {
145            // Another thread beat us — don’t increment the global next plugin index,
146            // just return existing
147            existing
148        }
149    }
150}
151
152/// We store our own instance data alongside the `Entity` instance itself.
153pub struct EntityRecord {
154    /// The total count of all entities of this type (i.e., the next index to assign).
155    pub(crate) entity_count: usize,
156    /// Lazily initialized `Entity` instance.
157    pub(crate) entity: OnceCell<Box<dyn Any>>,
158    /// A type-erased `Box<PropertyStore<E>>`, lazily initialized.
159    pub(crate) property_store: OnceCell<Box<dyn Any>>,
160}
161
162impl EntityRecord {
163    pub(crate) fn new() -> Self {
164        Self {
165            entity_count: 0,
166            entity: OnceCell::new(),
167            property_store: OnceCell::new(),
168        }
169    }
170}
171
172/// A wrapper around a vector of entities.
173pub struct EntityStore {
174    items: Vec<EntityRecord>,
175}
176
177impl Default for EntityStore {
178    fn default() -> Self {
179        EntityStore::new()
180    }
181}
182
183impl EntityStore {
184    /// Creates a new [`EntityStore`], allocating the exact number of slots as there are
185    /// registered [`Entity`]s.
186    ///
187    /// This method assumes all types implementing `Entity` have been implemented _correctly_.
188    /// This is one of the pitfalls of this pattern: there is no guarantee that types
189    /// implementing `Entity` followed the rules. We can have at least some confidence,
190    /// though, in their correctness by supplying a correct implementation via a macro.
191    pub fn new() -> Self {
192        let num_items = get_registered_entity_count();
193        Self {
194            items: (0..num_items).map(|_| EntityRecord::new()).collect(),
195        }
196    }
197
198    /// Fetches an immutable reference to the entity `E` from the registry. This
199    /// implementation lazily instantiates the item if it has not yet been instantiated.
200    #[must_use]
201    pub fn get<E: Entity>(&self) -> &E {
202        let index = E::id();
203        self.items
204        .get(index)
205        .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."))
206        .entity
207        .get_or_init(|| E::new_boxed())
208        .downcast_ref::<E>()
209        .expect("TypeID does not match registered entity type. You must use the `define_entity!` macro to create an entity.")
210    }
211
212    /// Fetches a mutable reference to the item `E` from the registry. This
213    /// implementation lazily instantiates the item if it has not yet been instantiated.
214    #[must_use]
215    pub fn get_mut<E: Entity>(&mut self) -> &mut E {
216        let index = E::id();
217
218        let record = self.items.get_mut(index).unwrap_or_else(|| {
219            panic!(
220                "No registered entity found with index = {index:?}. \
221             You must use the `define_entity!` macro to create an entity."
222            )
223        });
224
225        // Initialize if needed
226        if record.entity.get().is_none() {
227            record.entity.set(E::new_boxed()).unwrap();
228        }
229
230        // Now the `unwrap` on `get_mut` is guaranteed to succeed.
231        record.entity.get_mut().unwrap().downcast_mut::<E>().expect(
232            "TypeID does not match registered entity type. \
233             You must use the `define_entity!` macro to create an entity.",
234        )
235    }
236
237    /// Creates a new `EntityId` for the given `Entity` type `E`.
238    /// Increments the entity counter and returns the next valid ID.
239    pub(crate) fn new_entity_id<E: Entity>(&mut self) -> EntityId<E> {
240        let index = E::id();
241        let record = &mut self.items[index];
242        let id = record.entity_count;
243        record.entity_count += 1;
244        EntityId::new(id)
245    }
246
247    /// Returns a total count of all created entities of type `E`.
248    #[must_use]
249    pub fn get_entity_count<E: Entity>(&self) -> usize {
250        let index = E::id();
251        let record = &self.items[index];
252        record.entity_count
253    }
254
255    /// Returns a total count of all created entities of type `E`.
256    #[must_use]
257    pub fn get_entity_count_by_id(&self, id: usize) -> usize {
258        let record = &self.items[id];
259        record.entity_count
260    }
261
262    /// Returns an iterator over all valid `EntityId<E>`s
263    pub fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
264        let count = self.get_entity_count::<E>();
265        PopulationIterator::new(count)
266    }
267
268    pub fn get_property_store<E: Entity>(&self) -> &PropertyStore<E> {
269        let index = E::id();
270        let record = self.items
271                         .get(index)
272                         .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."));
273        let property_store = record
274            .property_store
275            .get_or_init(|| Box::new(PropertyStore::<E>::new()));
276        property_store.downcast_ref::<PropertyStore<E>>()
277                      .expect("TypeID does not match registered item type. You must use the `define_registered_item!` macro to create a registered item.")
278    }
279
280    pub fn get_property_store_mut<E: Entity>(&mut self) -> &mut PropertyStore<E> {
281        let index = E::id();
282        let record = self.items
283                         .get_mut(index)
284                         .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."));
285        let _ = record
286            .property_store
287            .get_or_init(|| Box::new(PropertyStore::<E>::new()));
288        let property_store = record.property_store.get_mut().unwrap();
289        property_store.downcast_mut::<PropertyStore<E>>()
290                      .expect("TypeID does not match registered item type. You must use the `define_registered_item!` macro to create a registered item.")
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use std::any::Any;
297    use std::sync::atomic::{AtomicUsize, Ordering};
298    use std::sync::{Arc, Barrier};
299    use std::thread;
300
301    use crate::entity::entity_store::{
302        add_to_entity_registry, get_registered_entity_count, initialize_entity_index, EntityStore,
303    };
304    use crate::entity::Entity;
305    use crate::{impl_entity, Context, ContextEntitiesExt, HashMap};
306    // Test item types
307    #[derive(Debug, Clone, PartialEq)]
308    pub struct TestItem1 {
309        value: usize,
310    }
311    impl Default for TestItem1 {
312        fn default() -> Self {
313            Self { value: 42 }
314        }
315    }
316
317    #[derive(Debug, Clone, PartialEq)]
318    pub struct TestItem2 {
319        name: String,
320    }
321    impl Default for TestItem2 {
322        fn default() -> Self {
323            TestItem2 {
324                name: "test".to_string(),
325            }
326        }
327    }
328
329    #[derive(Debug, Clone, PartialEq)]
330    pub struct TestItem3 {
331        data: Vec<u8>,
332    }
333    impl Default for TestItem3 {
334        fn default() -> Self {
335            TestItem3 {
336                data: vec![1, 2, 3],
337            }
338        }
339    }
340
341    // Implement RegisteredItem manually for testing without macro
342    impl_entity!(TestItem1);
343    impl_entity!(TestItem2);
344    impl_entity!(TestItem3);
345
346    // Test the internal synchronization mechanisms of `initialize_entity_index()`.
347    //
348    // It is convenient to only have a single test that mutates `NEXT_ENTITY_INDEX`,
349    // because we can assume no other thread is incrementing it and can therefore
350    // test the value of `NEXT_ENTITY_INDEX` at the beginning and then at the end of
351    // the test.
352    //
353    // Note that this doesn't really interfere with other tests involving `EntityStore`,
354    // because at worst `EntityStore` will just allocate addition slots for
355    // nonexistent items, which will never be requested with a `get()` call.
356    #[test]
357    fn test_initialize_item_index_concurrent() {
358        // Test 1: Try to initialize a single index from multiple threads simultaneously.
359        let initial_registered_items_count = get_registered_entity_count();
360
361        const NUM_THREADS: usize = 100;
362        let index = Arc::new(AtomicUsize::new(usize::MAX));
363        let barrier = Arc::new(Barrier::new(NUM_THREADS));
364
365        let handles: Vec<_> = (0..NUM_THREADS)
366            .map(|_| {
367                let index_clone = Arc::clone(&index);
368                let barrier_clone = Arc::clone(&barrier);
369
370                thread::spawn(move || {
371                    // Wait for all threads to be ready
372                    barrier_clone.wait();
373                    // All threads try to initialize at once
374                    initialize_entity_index(&index_clone)
375                })
376            })
377            .collect();
378
379        let results: Vec<usize> = handles.into_iter().map(|h| h.join().unwrap()).collect();
380
381        let first = results[0];
382
383        // The index should be initialized
384        assert_ne!(first, usize::MAX);
385        // All threads should get the same index
386        assert!(results.iter().all(|&r| r == first));
387        // And that index should be what was originally the next available index
388        assert_eq!(first, initial_registered_items_count);
389
390        // Test 2: Try to initialize multiple indices from multiple threads simultaneously.
391        //
392        // Creates 5 different entities (each with their own atomic). Initializes
393        // each from a separate thread. Verifies they receive sequential,
394        // unique indices. Confirms the global counter matches the entity count.
395
396        // W
397        let initial_registered_items_count = get_registered_entity_count();
398
399        // Create multiple different entities (each with their own atomic)
400        const NUM_ENTITIES: usize = 5;
401        let entities: Vec<_> = (0..NUM_ENTITIES)
402            .map(|_| Arc::new(AtomicUsize::new(usize::MAX)))
403            .collect();
404
405        let mut handles = vec![];
406
407        // Initialize each entity from a different thread
408        for entity in entities.iter() {
409            let entity_clone = Arc::clone(entity);
410            let handle = thread::spawn(move || initialize_entity_index(&entity_clone));
411            handles.push(handle);
412        }
413
414        // Collect results
415        let mut results = vec![];
416        for handle in handles {
417            results.push(handle.join().unwrap());
418        }
419
420        // Each entity should get a unique, sequential index starting with `initial_registered_items_count`.
421        results.sort();
422        for (i, &result) in results.iter().enumerate() {
423            assert_eq!(
424                result,
425                i + initial_registered_items_count,
426                "Entity should have index {}, got {}",
427                i,
428                result
429            );
430        }
431
432        // Test 3: Try to initialize multiple entities from multiple threads multiple times.
433
434        // We account for the fact that some entities have been initialized
435        // in their `ctors`, so the indices we create don't start with 0.
436        let initial_registered_items_count = get_registered_entity_count();
437
438        // Create 3 entities
439        let entity1 = Arc::new(AtomicUsize::new(usize::MAX));
440        let entity2 = Arc::new(AtomicUsize::new(usize::MAX));
441        let entity3 = Arc::new(AtomicUsize::new(usize::MAX));
442
443        let mut handles = vec![];
444
445        // Multiple threads racing on each of entity1, entity2, entity3
446        for _ in 0..5 {
447            let e1 = Arc::clone(&entity1);
448            handles.push(thread::spawn(move || initialize_entity_index(&e1)));
449
450            let e2 = Arc::clone(&entity2);
451            handles.push(thread::spawn(move || initialize_entity_index(&e2)));
452
453            let e3 = Arc::clone(&entity3);
454            handles.push(thread::spawn(move || initialize_entity_index(&e3)));
455        }
456
457        // Collect all results
458        let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
459
460        // Count occurrences of each index
461        let mut counts = HashMap::default();
462        for &result in &results {
463            *counts.entry(result).or_insert(0) += 1;
464        }
465
466        // Should have exactly 3 unique indices
467        assert_eq!(counts.len(), 3, "Should have 3 unique indices");
468
469        // Each index should appear exactly 5 times (one entity, 5 threads)
470        for (&idx, &count) in &counts {
471            assert_eq!(
472                count, 5,
473                "Index {} should appear 5 times, appeared {} times",
474                idx, count
475            );
476        }
477
478        // Global counter should be 3
479        assert_eq!(
480            get_registered_entity_count() - initial_registered_items_count,
481            3
482        );
483
484        // Each entity should have one of the indices
485        let indices: Vec<_> = vec![
486            entity1.load(Ordering::Acquire),
487            entity2.load(Ordering::Acquire),
488            entity3.load(Ordering::Acquire),
489        ];
490
491        let mut sorted_indices = indices.clone();
492        sorted_indices.sort_unstable();
493        // As before, we account for the fact that some entities have been
494        // initialized in their `ctors`, so the indices we created don't start at 0.
495        let expected_indices = vec![
496            initial_registered_items_count,
497            1 + initial_registered_items_count,
498            2 + initial_registered_items_count,
499        ];
500        assert_eq!(sorted_indices, expected_indices);
501    }
502
503    // Registering items is idempotent
504    #[test]
505    fn test_add_to_registry_idempotent() {
506        let index1 = TestItem1::id();
507        let index2 = TestItem2::id();
508        let index3 = TestItem3::id();
509
510        // All should be initialized (uninitialized indices are `usize::MAX`)
511        assert_ne!(index1, usize::MAX);
512        assert_ne!(index2, usize::MAX);
513        assert_ne!(index3, usize::MAX);
514
515        // Each should have a unique index
516        assert_ne!(index1, index2);
517        assert_ne!(index2, index3);
518        assert_ne!(index1, index3);
519
520        // Adding the same type multiple times should return the same index.
521        add_to_entity_registry::<TestItem1>();
522        add_to_entity_registry::<TestItem1>();
523        add_to_entity_registry::<TestItem1>();
524
525        let index_from_registry_1 = TestItem1::id();
526        let index_from_registry_2 = TestItem2::id();
527        let index_from_registry_3 = TestItem3::id();
528
529        assert_eq!(index1, index_from_registry_1);
530        assert_eq!(index2, index_from_registry_2);
531        assert_eq!(index3, index_from_registry_3);
532    }
533
534    // Getting items lazily initializes `Entity` instances
535    #[test]
536    fn test_registered_items_get() {
537        // Test mutable `EntityStore::get_mut`
538        {
539            let mut items = EntityStore::new();
540
541            let item1 = items.get_mut::<TestItem1>();
542            assert_eq!(item1.value, 42);
543            assert_eq!(TestItem1::name(), "TestItem1");
544
545            let item2 = items.get_mut::<TestItem2>();
546            assert_eq!(item2.name, "test");
547
548            let item3 = items.get_mut::<TestItem3>();
549            assert_eq!(item3.data, vec![1, 2, 3]);
550        }
551
552        // Test immutable `EntityStore::get`
553        {
554            let items = EntityStore::new();
555
556            let item1 = items.get::<TestItem1>();
557            assert_eq!(item1.value, 42);
558            assert_eq!(TestItem1::name(), "TestItem1");
559
560            let item2 = items.get::<TestItem2>();
561            assert_eq!(item2.name, "test");
562
563            let item3 = items.get::<TestItem3>();
564            assert_eq!(item3.data, vec![1, 2, 3]);
565        }
566    }
567
568    // Initialization happens once
569    #[test]
570    fn test_registered_items_get_cached() {
571        // Test immutable `EntityStore::get`
572        {
573            let items = EntityStore::new();
574
575            // Get the item twice
576            let item1_ref1 = items.get::<TestItem1>();
577            let item1_ref2 = items.get::<TestItem1>();
578
579            // Both should point to the same instance
580            assert!(std::ptr::eq(item1_ref1, item1_ref2));
581        }
582
583        // Test mutable `EntityStore::get_mut`
584        {
585            let mut items = EntityStore::new();
586
587            // Get the item twice. We can safely get multiple mutable pointers so long as we don't dereference them.
588            let item1_ptr1: *mut TestItem1 = items.get_mut::<TestItem1>();
589            let item1_ptr2: *mut TestItem1 = items.get_mut::<TestItem1>();
590
591            // Both should point to the same instance
592            assert!(std::ptr::eq(item1_ptr1, item1_ptr2));
593        }
594    }
595
596    #[test]
597    fn test_registered_items_get_mut() {
598        let mut items = EntityStore::new();
599
600        // Get mutable reference and modify
601        let item = items.get_mut::<TestItem1>();
602        assert_eq!(item.value, 42);
603        item.value = 100;
604
605        // Verify the change persisted
606        let item = items.get::<TestItem1>();
607        assert_eq!(item.value, 100);
608    }
609
610    #[test]
611    fn test_registered_items_multiple_items_mutated() {
612        let mut items = EntityStore::new();
613
614        // Read and mutate multiple items
615        let item1 = items.get_mut::<TestItem1>();
616        assert_eq!(item1.value, 42);
617        item1.value = 10;
618
619        let item2 = items.get_mut::<TestItem2>();
620        assert_eq!(item2.name, "test");
621        item2.name = "modified".to_string();
622
623        let item3 = items.get_mut::<TestItem3>();
624        assert_eq!(item3.data, vec![1, 2, 3]);
625        item3.data = vec![9, 8, 7];
626
627        // Verify all changes
628        assert_eq!(items.get::<TestItem1>().value, 10);
629        assert_eq!(items.get::<TestItem2>().name, "modified");
630        assert_eq!(items.get::<TestItem3>().data, vec![9, 8, 7]);
631    }
632
633    #[test]
634    #[should_panic(expected = "No registered entity found with index")]
635    fn test_registered_items_invalid_index() {
636        #[derive(Debug, Default)]
637        struct UnregisteredEntity;
638
639        // Intentionally implement `RegisteredItem` incorrectly.
640        impl Entity for UnregisteredEntity {
641            fn name() -> &'static str
642            where
643                Self: Sized,
644            {
645                "UnregisteredItem"
646            }
647
648            fn id() -> usize
649            where
650                Self: Sized,
651            {
652                87000 // An invalid index
653            }
654
655            fn as_any(&self) -> &dyn Any {
656                self
657            }
658            fn as_any_mut(&mut self) -> &mut dyn Any {
659                self
660            }
661        }
662
663        // Create items container with insufficient capacity
664        let items = EntityStore::new();
665
666        // This should panic because TestItem1's index doesn't exist
667        let _ = items.get::<UnregisteredEntity>();
668    }
669
670    #[test]
671    fn test_registered_item_trait_name() {
672        assert_eq!(TestItem1::name(), "TestItem1");
673        assert_eq!(TestItem2::name(), "TestItem2");
674        assert_eq!(TestItem3::name(), "TestItem3");
675    }
676
677    #[test]
678    fn test_registered_item_new_boxed() {
679        let boxed1 = TestItem1::new_boxed();
680        assert_eq!(boxed1.value, 42);
681
682        let boxed2 = TestItem2::new_boxed();
683        assert_eq!(boxed2.name, "test");
684
685        let boxed3 = TestItem3::new_boxed();
686        assert_eq!(boxed3.data, vec![1, 2, 3]);
687    }
688
689    #[test]
690    fn test_box_dyn_registered_item_type_alias() {
691        let item = TestItem1::new_boxed();
692        assert_eq!(
693            (item as Box<dyn Any>)
694                .downcast_ref::<TestItem1>()
695                .unwrap()
696                .value,
697            42
698        );
699    }
700
701    #[test]
702    fn test_entity_iterator() {
703        let mut context = Context::new();
704
705        // Add different numbers of entities for each type
706        // Note: add_entity returns Result<EntityId<E>, ...>, we unwrap for the test.
707        for _ in 0..5 {
708            context.add_entity::<TestItem1, _>(()).unwrap();
709        }
710        for _ in 0..3 {
711            context.add_entity::<TestItem2, _>(()).unwrap();
712        }
713        // TestItem3 remains at 0 for now
714
715        // 1. Verify counts
716        assert_eq!(context.get_entity_count::<TestItem1>(), 5);
717        assert_eq!(context.get_entity_count::<TestItem2>(), 3);
718        assert_eq!(context.get_entity_count::<TestItem3>(), 0);
719
720        // 2. Verify iterators
721        let iter1 = context.get_entity_iterator::<TestItem1>();
722        let results1: Vec<_> = iter1.collect();
723        assert_eq!(results1.len(), 5);
724        // Verify ID sequence (starts at 0)
725        for (i, id) in results1.into_iter().enumerate() {
726            assert_eq!(id.0, i);
727        }
728
729        let iter2 = context.get_entity_iterator::<TestItem2>();
730        assert_eq!(iter2.count(), 3);
731
732        let mut iter3 = context.get_entity_iterator::<TestItem3>();
733        assert!(iter3.next().is_none());
734
735        // 3. Verify iterator snapshot behavior
736        // Iterators created now should not see entities added later
737        let snapshot_iter = context.get_entity_iterator::<TestItem1>();
738
739        context.add_entity::<TestItem1, _>(()).unwrap();
740
741        assert_eq!(context.get_entity_count::<TestItem1>(), 6);
742        assert_eq!(snapshot_iter.count(), 5); // Still sees original population
743        assert_eq!(context.get_entity_iterator::<TestItem1>().count(), 6); // New iterator sees 6
744    }
745}