Skip to main content

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    #[must_use]
192    pub fn new() -> Self {
193        let num_items = get_registered_entity_count();
194        Self {
195            items: (0..num_items).map(|_| EntityRecord::new()).collect(),
196        }
197    }
198
199    /// Fetches an immutable reference to the entity `E` from the registry. This
200    /// implementation lazily instantiates the item if it has not yet been instantiated.
201    #[must_use]
202    pub fn get<E: Entity>(&self) -> &E {
203        let index = E::id();
204        self.items
205        .get(index)
206        .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."))
207        .entity
208        .get_or_init(|| E::new_boxed())
209        .downcast_ref::<E>()
210        .expect("TypeID does not match registered entity type. You must use the `define_entity!` macro to create an entity.")
211    }
212
213    /// Fetches a mutable reference to the item `E` from the registry. This
214    /// implementation lazily instantiates the item if it has not yet been instantiated.
215    #[must_use]
216    pub fn get_mut<E: Entity>(&mut self) -> &mut E {
217        let index = E::id();
218
219        let record = self.items.get_mut(index).unwrap_or_else(|| {
220            panic!(
221                "No registered entity found with index = {index:?}. \
222             You must use the `define_entity!` macro to create an entity."
223            )
224        });
225
226        // Initialize if needed
227        if record.entity.get().is_none() {
228            record.entity.set(E::new_boxed()).unwrap();
229        }
230
231        // Now the `unwrap` on `get_mut` is guaranteed to succeed.
232        record.entity.get_mut().unwrap().downcast_mut::<E>().expect(
233            "TypeID does not match registered entity type. \
234             You must use the `define_entity!` macro to create an entity.",
235        )
236    }
237
238    /// Creates a new `EntityId` for the given `Entity` type `E`.
239    /// Increments the entity counter and returns the next valid ID.
240    pub(crate) fn new_entity_id<E: Entity>(&mut self) -> EntityId<E> {
241        let index = E::id();
242        let record = &mut self.items[index];
243        let id = record.entity_count;
244        record.entity_count += 1;
245        EntityId::new(id)
246    }
247
248    /// Returns a total count of all created entities of type `E`.
249    #[must_use]
250    pub fn get_entity_count<E: Entity>(&self) -> usize {
251        let index = E::id();
252        let record = &self.items[index];
253        record.entity_count
254    }
255
256    /// Returns a total count of all created entities of type `E`.
257    #[must_use]
258    pub fn get_entity_count_by_id(&self, id: usize) -> usize {
259        let record = &self.items[id];
260        record.entity_count
261    }
262
263    /// Returns an iterator over all valid `EntityId<E>`s
264    #[must_use]
265    pub fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
266        let count = self.get_entity_count::<E>();
267        PopulationIterator::new(count)
268    }
269
270    #[must_use]
271    pub fn get_property_store<E: Entity>(&self) -> &PropertyStore<E> {
272        let index = E::id();
273        let record = self.items
274                         .get(index)
275                         .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."));
276        let property_store = record
277            .property_store
278            .get_or_init(|| Box::new(PropertyStore::<E>::new()));
279        property_store.downcast_ref::<PropertyStore<E>>()
280                      .expect("TypeID does not match registered item type. You must use the `define_registered_item!` macro to create a registered item.")
281    }
282
283    pub fn get_property_store_mut<E: Entity>(&mut self) -> &mut PropertyStore<E> {
284        let index = E::id();
285        let record = self.items
286                         .get_mut(index)
287                         .unwrap_or_else(|| panic!("No registered entity found with index = {index:?}. You must use the `define_entity!` macro to create an entity."));
288        let _ = record
289            .property_store
290            .get_or_init(|| Box::new(PropertyStore::<E>::new()));
291        let property_store = record.property_store.get_mut().unwrap();
292        property_store.downcast_mut::<PropertyStore<E>>()
293                      .expect("TypeID does not match registered item type. You must use the `define_registered_item!` macro to create a registered item.")
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use std::any::Any;
300    use std::sync::atomic::{AtomicUsize, Ordering};
301    use std::sync::{Arc, Barrier};
302    use std::thread;
303
304    use crate::entity::entity_store::{
305        add_to_entity_registry, get_registered_entity_count, initialize_entity_index, EntityStore,
306    };
307    use crate::entity::Entity;
308    use crate::{impl_entity, with, Context, ContextEntitiesExt, HashMap};
309    // Test item types
310    #[derive(Debug, Clone, PartialEq)]
311    pub struct TestItem1 {
312        value: usize,
313    }
314    impl Default for TestItem1 {
315        fn default() -> Self {
316            Self { value: 42 }
317        }
318    }
319
320    #[derive(Debug, Clone, PartialEq)]
321    pub struct TestItem2 {
322        name: String,
323    }
324    impl Default for TestItem2 {
325        fn default() -> Self {
326            TestItem2 {
327                name: "test".to_string(),
328            }
329        }
330    }
331
332    #[derive(Debug, Clone, PartialEq)]
333    pub struct TestItem3 {
334        data: Vec<u8>,
335    }
336    impl Default for TestItem3 {
337        fn default() -> Self {
338            TestItem3 {
339                data: vec![1, 2, 3],
340            }
341        }
342    }
343
344    // Implement RegisteredItem manually for testing without macro
345    impl_entity!(TestItem1);
346    impl_entity!(TestItem2);
347    impl_entity!(TestItem3);
348
349    // Test the internal synchronization mechanisms of `initialize_entity_index()`.
350    //
351    // It is convenient to only have a single test that mutates `NEXT_ENTITY_INDEX`,
352    // because we can assume no other thread is incrementing it and can therefore
353    // test the value of `NEXT_ENTITY_INDEX` at the beginning and then at the end of
354    // the test.
355    //
356    // Note that this doesn't really interfere with other tests involving `EntityStore`,
357    // because at worst `EntityStore` will just allocate addition slots for
358    // nonexistent items, which will never be requested with a `get()` call.
359    #[test]
360    fn test_initialize_item_index_concurrent() {
361        // Test 1: Try to initialize a single index from multiple threads simultaneously.
362        let initial_registered_items_count = get_registered_entity_count();
363
364        const NUM_THREADS: usize = 100;
365        let index = Arc::new(AtomicUsize::new(usize::MAX));
366        let barrier = Arc::new(Barrier::new(NUM_THREADS));
367
368        let handles: Vec<_> = (0..NUM_THREADS)
369            .map(|_| {
370                let index_clone = Arc::clone(&index);
371                let barrier_clone = Arc::clone(&barrier);
372
373                thread::spawn(move || {
374                    // Wait for all threads to be ready
375                    barrier_clone.wait();
376                    // All threads try to initialize at once
377                    initialize_entity_index(&index_clone)
378                })
379            })
380            .collect();
381
382        let results: Vec<usize> = handles.into_iter().map(|h| h.join().unwrap()).collect();
383
384        let first = results[0];
385
386        // The index should be initialized
387        assert_ne!(first, usize::MAX);
388        // All threads should get the same index
389        assert!(results.iter().all(|&r| r == first));
390        // And that index should be what was originally the next available index
391        assert_eq!(first, initial_registered_items_count);
392
393        // Test 2: Try to initialize multiple indices from multiple threads simultaneously.
394        //
395        // Creates 5 different entities (each with their own atomic). Initializes
396        // each from a separate thread. Verifies they receive sequential,
397        // unique indices. Confirms the global counter matches the entity count.
398
399        // W
400        let initial_registered_items_count = get_registered_entity_count();
401
402        // Create multiple different entities (each with their own atomic)
403        const NUM_ENTITIES: usize = 5;
404        let entities: Vec<_> = (0..NUM_ENTITIES)
405            .map(|_| Arc::new(AtomicUsize::new(usize::MAX)))
406            .collect();
407
408        let mut handles = vec![];
409
410        // Initialize each entity from a different thread
411        for entity in entities.iter() {
412            let entity_clone = Arc::clone(entity);
413            let handle = thread::spawn(move || initialize_entity_index(&entity_clone));
414            handles.push(handle);
415        }
416
417        // Collect results
418        let mut results = vec![];
419        for handle in handles {
420            results.push(handle.join().unwrap());
421        }
422
423        // Each entity should get a unique, sequential index starting with `initial_registered_items_count`.
424        results.sort();
425        for (i, &result) in results.iter().enumerate() {
426            assert_eq!(
427                result,
428                i + initial_registered_items_count,
429                "Entity should have index {}, got {}",
430                i,
431                result
432            );
433        }
434
435        // Test 3: Try to initialize multiple entities from multiple threads multiple times.
436
437        // We account for the fact that some entities have been initialized
438        // in their `ctors`, so the indices we create don't start with 0.
439        let initial_registered_items_count = get_registered_entity_count();
440
441        // Create 3 entities
442        let entity1 = Arc::new(AtomicUsize::new(usize::MAX));
443        let entity2 = Arc::new(AtomicUsize::new(usize::MAX));
444        let entity3 = Arc::new(AtomicUsize::new(usize::MAX));
445
446        let mut handles = vec![];
447
448        // Multiple threads racing on each of entity1, entity2, entity3
449        for _ in 0..5 {
450            let e1 = Arc::clone(&entity1);
451            handles.push(thread::spawn(move || initialize_entity_index(&e1)));
452
453            let e2 = Arc::clone(&entity2);
454            handles.push(thread::spawn(move || initialize_entity_index(&e2)));
455
456            let e3 = Arc::clone(&entity3);
457            handles.push(thread::spawn(move || initialize_entity_index(&e3)));
458        }
459
460        // Collect all results
461        let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
462
463        // Count occurrences of each index
464        let mut counts = HashMap::default();
465        for &result in &results {
466            *counts.entry(result).or_insert(0) += 1;
467        }
468
469        // Should have exactly 3 unique indices
470        assert_eq!(counts.len(), 3, "Should have 3 unique indices");
471
472        // Each index should appear exactly 5 times (one entity, 5 threads)
473        for (&idx, &count) in &counts {
474            assert_eq!(
475                count, 5,
476                "Index {} should appear 5 times, appeared {} times",
477                idx, count
478            );
479        }
480
481        // Global counter should be 3
482        assert_eq!(
483            get_registered_entity_count() - initial_registered_items_count,
484            3
485        );
486
487        // Each entity should have one of the indices
488        let indices: Vec<_> = vec![
489            entity1.load(Ordering::Acquire),
490            entity2.load(Ordering::Acquire),
491            entity3.load(Ordering::Acquire),
492        ];
493
494        let mut sorted_indices = indices.clone();
495        sorted_indices.sort_unstable();
496        // As before, we account for the fact that some entities have been
497        // initialized in their `ctors`, so the indices we created don't start at 0.
498        let expected_indices = vec![
499            initial_registered_items_count,
500            1 + initial_registered_items_count,
501            2 + initial_registered_items_count,
502        ];
503        assert_eq!(sorted_indices, expected_indices);
504    }
505
506    // Registering items is idempotent
507    #[test]
508    fn test_add_to_registry_idempotent() {
509        let index1 = TestItem1::id();
510        let index2 = TestItem2::id();
511        let index3 = TestItem3::id();
512
513        // All should be initialized (uninitialized indices are `usize::MAX`)
514        assert_ne!(index1, usize::MAX);
515        assert_ne!(index2, usize::MAX);
516        assert_ne!(index3, usize::MAX);
517
518        // Each should have a unique index
519        assert_ne!(index1, index2);
520        assert_ne!(index2, index3);
521        assert_ne!(index1, index3);
522
523        // Adding the same type multiple times should return the same index.
524        add_to_entity_registry::<TestItem1>();
525        add_to_entity_registry::<TestItem1>();
526        add_to_entity_registry::<TestItem1>();
527
528        let index_from_registry_1 = TestItem1::id();
529        let index_from_registry_2 = TestItem2::id();
530        let index_from_registry_3 = TestItem3::id();
531
532        assert_eq!(index1, index_from_registry_1);
533        assert_eq!(index2, index_from_registry_2);
534        assert_eq!(index3, index_from_registry_3);
535    }
536
537    // Getting items lazily initializes `Entity` instances
538    #[test]
539    fn test_registered_items_get() {
540        // Test mutable `EntityStore::get_mut`
541        {
542            let mut items = EntityStore::new();
543
544            let item1 = items.get_mut::<TestItem1>();
545            assert_eq!(item1.value, 42);
546            assert_eq!(TestItem1::name(), "TestItem1");
547
548            let item2 = items.get_mut::<TestItem2>();
549            assert_eq!(item2.name, "test");
550
551            let item3 = items.get_mut::<TestItem3>();
552            assert_eq!(item3.data, vec![1, 2, 3]);
553        }
554
555        // Test immutable `EntityStore::get`
556        {
557            let items = EntityStore::new();
558
559            let item1 = items.get::<TestItem1>();
560            assert_eq!(item1.value, 42);
561            assert_eq!(TestItem1::name(), "TestItem1");
562
563            let item2 = items.get::<TestItem2>();
564            assert_eq!(item2.name, "test");
565
566            let item3 = items.get::<TestItem3>();
567            assert_eq!(item3.data, vec![1, 2, 3]);
568        }
569    }
570
571    // Initialization happens once
572    #[test]
573    fn test_registered_items_get_cached() {
574        // Test immutable `EntityStore::get`
575        {
576            let items = EntityStore::new();
577
578            // Get the item twice
579            let item1_ref1 = items.get::<TestItem1>();
580            let item1_ref2 = items.get::<TestItem1>();
581
582            // Both should point to the same instance
583            assert!(std::ptr::eq(item1_ref1, item1_ref2));
584        }
585
586        // Test mutable `EntityStore::get_mut`
587        {
588            let mut items = EntityStore::new();
589
590            // Get the item twice. We can safely get multiple mutable pointers so long as we don't dereference them.
591            let item1_ptr1: *mut TestItem1 = items.get_mut::<TestItem1>();
592            let item1_ptr2: *mut TestItem1 = items.get_mut::<TestItem1>();
593
594            // Both should point to the same instance
595            assert!(std::ptr::eq(item1_ptr1, item1_ptr2));
596        }
597    }
598
599    #[test]
600    fn test_registered_items_get_mut() {
601        let mut items = EntityStore::new();
602
603        // Get mutable reference and modify
604        let item = items.get_mut::<TestItem1>();
605        assert_eq!(item.value, 42);
606        item.value = 100;
607
608        // Verify the change persisted
609        let item = items.get::<TestItem1>();
610        assert_eq!(item.value, 100);
611    }
612
613    #[test]
614    fn test_registered_items_multiple_items_mutated() {
615        let mut items = EntityStore::new();
616
617        // Read and mutate multiple items
618        let item1 = items.get_mut::<TestItem1>();
619        assert_eq!(item1.value, 42);
620        item1.value = 10;
621
622        let item2 = items.get_mut::<TestItem2>();
623        assert_eq!(item2.name, "test");
624        item2.name = "modified".to_string();
625
626        let item3 = items.get_mut::<TestItem3>();
627        assert_eq!(item3.data, vec![1, 2, 3]);
628        item3.data = vec![9, 8, 7];
629
630        // Verify all changes
631        assert_eq!(items.get::<TestItem1>().value, 10);
632        assert_eq!(items.get::<TestItem2>().name, "modified");
633        assert_eq!(items.get::<TestItem3>().data, vec![9, 8, 7]);
634    }
635
636    #[test]
637    #[should_panic(expected = "No registered entity found with index")]
638    fn test_registered_items_invalid_index() {
639        #[derive(Debug, Default)]
640        struct UnregisteredEntity;
641
642        // Intentionally implement `RegisteredItem` incorrectly.
643        impl Entity for UnregisteredEntity {
644            fn name() -> &'static str
645            where
646                Self: Sized,
647            {
648                "UnregisteredItem"
649            }
650
651            fn id() -> usize
652            where
653                Self: Sized,
654            {
655                87000 // An invalid index
656            }
657
658            fn as_any(&self) -> &dyn Any {
659                self
660            }
661            fn as_any_mut(&mut self) -> &mut dyn Any {
662                self
663            }
664        }
665
666        // Create items container with insufficient capacity
667        let items = EntityStore::new();
668
669        // This should panic because TestItem1's index doesn't exist
670        let _ = items.get::<UnregisteredEntity>();
671    }
672
673    #[test]
674    fn test_registered_item_trait_name() {
675        assert_eq!(TestItem1::name(), "TestItem1");
676        assert_eq!(TestItem2::name(), "TestItem2");
677        assert_eq!(TestItem3::name(), "TestItem3");
678    }
679
680    #[test]
681    fn test_registered_item_new_boxed() {
682        let boxed1 = TestItem1::new_boxed();
683        assert_eq!(boxed1.value, 42);
684
685        let boxed2 = TestItem2::new_boxed();
686        assert_eq!(boxed2.name, "test");
687
688        let boxed3 = TestItem3::new_boxed();
689        assert_eq!(boxed3.data, vec![1, 2, 3]);
690    }
691
692    #[test]
693    fn test_box_dyn_registered_item_type_alias() {
694        let item = TestItem1::new_boxed();
695        assert_eq!(
696            (item as Box<dyn Any>)
697                .downcast_ref::<TestItem1>()
698                .unwrap()
699                .value,
700            42
701        );
702    }
703
704    #[test]
705    fn test_entity_iterator() {
706        let mut context = Context::new();
707
708        // Add different numbers of entities for each type
709        // Note: add_entity returns Result<EntityId<E>, ...>, we unwrap for the test.
710        for _ in 0..5 {
711            context
712                .add_entity::<TestItem1, _>(with!(TestItem1))
713                .unwrap();
714        }
715        for _ in 0..3 {
716            context
717                .add_entity::<TestItem2, _>(with!(TestItem2))
718                .unwrap();
719        }
720        // TestItem3 remains at 0 for now
721
722        // 1. Verify counts
723        assert_eq!(context.get_entity_count::<TestItem1>(), 5);
724        assert_eq!(context.get_entity_count::<TestItem2>(), 3);
725        assert_eq!(context.get_entity_count::<TestItem3>(), 0);
726
727        // 2. Verify iterators
728        let iter1 = context.get_entity_iterator::<TestItem1>();
729        let results1: Vec<_> = iter1.collect();
730        assert_eq!(results1.len(), 5);
731        // Verify ID sequence (starts at 0)
732        for (i, id) in results1.into_iter().enumerate() {
733            assert_eq!(id.0, i);
734        }
735
736        let iter2 = context.get_entity_iterator::<TestItem2>();
737        assert_eq!(iter2.count(), 3);
738
739        let mut iter3 = context.get_entity_iterator::<TestItem3>();
740        assert!(iter3.next().is_none());
741
742        // 3. Verify iterator snapshot behavior
743        // Iterators created now should not see entities added later
744        let snapshot_iter = context.get_entity_iterator::<TestItem1>();
745
746        context
747            .add_entity::<TestItem1, _>(with!(TestItem1))
748            .unwrap();
749
750        assert_eq!(context.get_entity_count::<TestItem1>(), 6);
751        assert_eq!(snapshot_iter.count(), 5); // Still sees original population
752        assert_eq!(context.get_entity_iterator::<TestItem1>().count(), 6); // New iterator sees 6
753    }
754}