ferravex_ecs/
lib.rs

1//! Original, dependency-free entity/component storage for small Ferravex games.
2//!
3//! Components live in one sparse, contiguous slot array per Rust type. Entity
4//! generations prevent stale handles from reaching reused slots. Iteration visits
5//! live entity slots in stable order. Runtime component borrowing uses standard
6//! library [`Ref`] / [`RefMut`] guards, so overlapping queries cannot create
7//! aliased mutable references, even if an iterator is retained or abandoned.
8//!
9//! ```
10//! use ferravex_ecs::{Entity, World};
11//! let mut world = World::new();
12//! let player = world.spawn((10_i32, 2.0_f32));
13//! for (entity, mut health, speed) in &mut world.query::<(Entity, &mut i32, &f32)>() {
14//!     assert_eq!(entity, player);
15//!     *health += *speed as i32;
16//! }
17//! assert_eq!(*world.get::<&i32>(player).unwrap(), 12);
18//! world.despawn(player).unwrap();
19//! assert!(!world.contains(player));
20//! ```
21//!
22//! Query results are borrow guards rather than naked references. Bind mutable
23//! results with `mut`, as above. `try_query` and `try_iter` report conflicts as
24//! errors; `query` and `iter` panic on invalid/overlapping borrows. Structural
25//! operations require `&mut World`, so Rust prevents them while guards exist.
26//!
27//! ```compile_fail
28//! use ferravex_ecs::World;
29//! let mut world = World::new();
30//! let entity = world.spawn((42_i32,));
31//! let component = world.get::<&i32>(entity).unwrap();
32//! world.despawn(entity).unwrap(); // A live guard borrows the world.
33//! println!("{}", *component);
34//! ```
35#![forbid(unsafe_code)]
36
37use std::any::{Any, TypeId, type_name};
38use std::cell::RefCell;
39pub use std::cell::{Ref, RefMut};
40use std::collections::HashMap;
41use std::fmt;
42use std::marker::PhantomData;
43
44/// Opaque slot identity. A despawned entity never becomes valid again in its world.
45/// Handles are world-local; two separate worlds may issue the same identity.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
47pub struct Entity {
48    index: u32,
49    generation: u32,
50}
51
52impl Entity {
53    /// Sparse slot index, useful for diagnostics. It is not a unique identity alone.
54    pub fn id(self) -> u32 {
55        self.index
56    }
57    pub fn generation(self) -> u32 {
58        self.generation
59    }
60    /// A compact representation for local persistence. Validate with World::contains.
61    pub fn to_bits(self) -> u64 {
62        (u64::from(self.generation) << 32) | u64::from(self.index)
63    }
64    /// Reconstructs a handle; this does not prove that its entity exists.
65    pub fn from_bits(bits: u64) -> Self {
66        Self {
67            index: bits as u32,
68            generation: (bits >> 32) as u32,
69        }
70    }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum Error {
75    NoSuchEntity(Entity),
76    MissingComponent(&'static str),
77    BorrowConflict(&'static str),
78    DuplicateComponent(&'static str),
79    ConflictingQuery(&'static str),
80}
81
82impl fmt::Display for Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::NoSuchEntity(entity) => write!(f, "entity {entity:?} is not alive"),
86            Self::MissingComponent(name) => write!(f, "entity has no {name} component"),
87            Self::BorrowConflict(name) => {
88                write!(f, "{name} component is already incompatibly borrowed")
89            }
90            Self::DuplicateComponent(name) => write!(f, "bundle contains {name} more than once"),
91            Self::ConflictingQuery(name) => {
92                write!(f, "query requests overlapping mutable access to {name}")
93            }
94        }
95    }
96}
97impl std::error::Error for Error {}
98
99#[derive(Default)]
100struct Slot {
101    generation: u32,
102    alive: bool,
103}
104
105trait ErasedStorage {
106    fn as_any(&self) -> &dyn Any;
107    fn as_any_mut(&mut self) -> &mut dyn Any;
108    fn erase(&mut self, index: usize);
109}
110
111struct Storage<T> {
112    slots: Vec<Option<RefCell<T>>>,
113}
114
115impl<T> Default for Storage<T> {
116    fn default() -> Self {
117        Self { slots: Vec::new() }
118    }
119}
120
121impl<T: 'static> ErasedStorage for Storage<T> {
122    fn as_any(&self) -> &dyn Any {
123        self
124    }
125    fn as_any_mut(&mut self) -> &mut dyn Any {
126        self
127    }
128    fn erase(&mut self, index: usize) {
129        if let Some(slot) = self.slots.get_mut(index) {
130            *slot = None;
131        }
132    }
133}
134
135/// Sparse typed storage with generational entities and checked component borrows.
136///
137/// RefCell deliberately makes World !Sync. Parallel mutation should use separate
138/// worlds or explicit synchronization around the whole world.
139#[derive(Default)]
140pub struct World {
141    slots: Vec<Slot>,
142    free: Vec<u32>,
143    storages: HashMap<TypeId, Box<dyn ErasedStorage>>,
144    len: usize,
145}
146
147impl World {
148    pub fn new() -> Self {
149        Self::default()
150    }
151    pub fn len(&self) -> usize {
152        self.len
153    }
154    pub fn is_empty(&self) -> bool {
155        self.len == 0
156    }
157
158    pub fn contains(&self, entity: Entity) -> bool {
159        self.slots
160            .get(entity.index as usize)
161            .is_some_and(|slot| slot.alive && slot.generation == entity.generation)
162    }
163
164    /// Spawns one entity containing zero to four components.
165    /// Panics if a bundle repeats a component type; no entity is allocated in that case.
166    pub fn spawn<B: Bundle>(&mut self, bundle: B) -> Entity {
167        self.try_spawn(bundle)
168            .unwrap_or_else(|error| panic!("{error}"))
169    }
170
171    pub fn try_spawn<B: Bundle>(&mut self, bundle: B) -> Result<Entity, Error> {
172        validate_bundle::<B>()?;
173        let index = if let Some(index) = self.free.pop() {
174            index
175        } else {
176            let index = u32::try_from(self.slots.len()).expect("entity slot capacity exceeded");
177            self.slots.push(Slot::default());
178            index
179        };
180        let slot = &mut self.slots[index as usize];
181        slot.alive = true;
182        let entity = Entity {
183            index,
184            generation: slot.generation,
185        };
186        self.len += 1;
187        bundle.write(self, index as usize);
188        Ok(entity)
189    }
190
191    /// Drops every component and invalidates the handle before reusing its slot.
192    pub fn despawn(&mut self, entity: Entity) -> Result<(), Error> {
193        self.validate(entity)?;
194        let slot = &mut self.slots[entity.index as usize];
195        slot.alive = false;
196        self.len -= 1;
197        // Never wrap a generation: a fully exhausted slot is permanently retired.
198        let next_generation = slot.generation.checked_add(1);
199        if let Some(next) = next_generation {
200            slot.generation = next;
201        }
202        for storage in self.storages.values_mut() {
203            storage.erase(entity.index as usize);
204        }
205        // A panicking component destructor leaves the slot retired, never recycled
206        // with leftover components from the despawned entity.
207        if next_generation.is_some() {
208            self.free.push(entity.index);
209        }
210        Ok(())
211    }
212
213    /// Inserts or replaces a bundle's components. Other components remain unchanged.
214    /// Invalid identities and duplicate bundle types are rejected before any writes.
215    pub fn insert<B: Bundle>(&mut self, entity: Entity, bundle: B) -> Result<(), Error> {
216        self.validate(entity)?;
217        validate_bundle::<B>()?;
218        bundle.write(self, entity.index as usize);
219        Ok(())
220    }
221
222    pub fn remove<T: 'static>(&mut self, entity: Entity) -> Result<T, Error> {
223        self.validate(entity)?;
224        self.storages
225            .get_mut(&TypeId::of::<T>())
226            .and_then(|s| s.as_any_mut().downcast_mut::<Storage<T>>())
227            .and_then(|storage| storage.slots.get_mut(entity.index as usize))
228            .and_then(Option::take)
229            .map(RefCell::into_inner)
230            .ok_or(Error::MissingComponent(type_name::<T>()))
231    }
232
233    pub fn has<T: 'static>(&self, entity: Entity) -> bool {
234        self.contains(entity) && self.cell::<T>(entity.index as usize).is_some()
235    }
236
237    /// Reads or mutates a typed component through an explicit access type.
238    /// Use get::<&T> or get::<&mut T>; returned guards enforce runtime aliasing.
239    pub fn get<A: Access>(&self, entity: Entity) -> Result<A::Item<'_>, Error> {
240        self.validate(entity)?;
241        A::fetch(self, entity)
242    }
243
244    pub fn get_mut<T: 'static>(&self, entity: Entity) -> Result<RefMut<'_, T>, Error> {
245        self.get::<&mut T>(entity)
246    }
247
248    /// Queries up to four component access types, optionally including Entity.
249    /// Repeated shared access is valid; any repeated mutable access is rejected.
250    pub fn query<Q: Query>(&self) -> QueryBorrow<'_, Q> {
251        self.try_query().unwrap_or_else(|error| panic!("{error}"))
252    }
253
254    pub fn try_query<Q: Query>(&self) -> Result<QueryBorrow<'_, Q>, Error> {
255        Q::validate()?;
256        Ok(QueryBorrow {
257            world: self,
258            marker: PhantomData,
259        })
260    }
261
262    /// Live identities in stable slot order, without borrowing any components.
263    pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
264        self.slots.iter().enumerate().filter_map(|(index, slot)| {
265            slot.alive.then_some(Entity {
266                index: index as u32,
267                generation: slot.generation,
268            })
269        })
270    }
271
272    fn validate(&self, entity: Entity) -> Result<(), Error> {
273        if self.contains(entity) {
274            Ok(())
275        } else {
276            Err(Error::NoSuchEntity(entity))
277        }
278    }
279
280    fn cell<T: 'static>(&self, index: usize) -> Option<&RefCell<T>> {
281        self.storages
282            .get(&TypeId::of::<T>())
283            .and_then(|s| s.as_any().downcast_ref::<Storage<T>>())
284            .and_then(|storage| storage.slots.get(index))
285            .and_then(Option::as_ref)
286    }
287
288    fn put<T: 'static>(&mut self, index: usize, value: T) {
289        let storage = self
290            .storages
291            .entry(TypeId::of::<T>())
292            .or_insert_with(|| Box::<Storage<T>>::default())
293            .as_any_mut()
294            .downcast_mut::<Storage<T>>()
295            .expect("component storage type matches its TypeId");
296        if storage.slots.len() <= index {
297            storage.slots.resize_with(index + 1, || None);
298        }
299        storage.slots[index] = Some(RefCell::new(value));
300    }
301}
302
303mod sealed {
304    pub trait Bundle {}
305    pub trait Access {}
306    pub trait Query {}
307}
308
309/// Tuple of zero to four owned component values accepted by spawn and insert.
310/// Implementations are sealed so validation and insertion cannot disagree.
311pub trait Bundle: sealed::Bundle {
312    #[doc(hidden)]
313    fn component_types(types: &mut Vec<(TypeId, &'static str)>);
314    #[doc(hidden)]
315    fn write(self, world: &mut World, index: usize);
316}
317
318impl sealed::Bundle for () {}
319impl Bundle for () {
320    fn component_types(_: &mut Vec<(TypeId, &'static str)>) {}
321    fn write(self, _: &mut World, _: usize) {}
322}
323
324macro_rules! bundle {
325    ($($name:ident : $index:tt),+) => {
326        impl<$($name: 'static),+> sealed::Bundle for ($($name,)+) {}
327        impl<$($name: 'static),+> Bundle for ($($name,)+) {
328            fn component_types(types: &mut Vec<(TypeId, &'static str)>) {
329                $(types.push((TypeId::of::<$name>(), type_name::<$name>()));)+
330            }
331            fn write(self, world: &mut World, index: usize) {
332                $(world.put(index, self.$index);)+
333            }
334        }
335    }
336}
337bundle!(A: 0);
338bundle!(A: 0, B: 1);
339bundle!(A: 0, B: 1, C: 2);
340bundle!(A: 0, B: 1, C: 2, D: 3);
341
342fn validate_bundle<B: Bundle>() -> Result<(), Error> {
343    let mut types = Vec::new();
344    B::component_types(&mut types);
345    for (index, (id, name)) in types.iter().enumerate() {
346        if types[..index].iter().any(|other| other.0 == *id) {
347            return Err(Error::DuplicateComponent(name));
348        }
349    }
350    Ok(())
351}
352
353#[doc(hidden)]
354pub struct AccessInfo {
355    id: TypeId,
356    name: &'static str,
357    mutable: bool,
358}
359
360/// A single component access (&T or &mut T), or the entity identity itself.
361pub trait Access: sealed::Access {
362    type Item<'world>;
363    #[doc(hidden)]
364    fn info() -> Option<AccessInfo>;
365    #[doc(hidden)]
366    fn matches(world: &World, entity: Entity) -> bool;
367    #[doc(hidden)]
368    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error>;
369}
370
371impl<T: 'static> sealed::Access for &T {}
372impl<T: 'static> Access for &T {
373    type Item<'world> = Ref<'world, T>;
374    fn info() -> Option<AccessInfo> {
375        Some(AccessInfo {
376            id: TypeId::of::<T>(),
377            name: type_name::<T>(),
378            mutable: false,
379        })
380    }
381    fn matches(world: &World, entity: Entity) -> bool {
382        world.has::<T>(entity)
383    }
384    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error> {
385        world.validate(entity)?;
386        world
387            .cell::<T>(entity.index as usize)
388            .ok_or(Error::MissingComponent(type_name::<T>()))?
389            .try_borrow()
390            .map_err(|_| Error::BorrowConflict(type_name::<T>()))
391    }
392}
393impl<T: 'static> sealed::Access for &mut T {}
394impl<T: 'static> Access for &mut T {
395    type Item<'world> = RefMut<'world, T>;
396    fn info() -> Option<AccessInfo> {
397        Some(AccessInfo {
398            id: TypeId::of::<T>(),
399            name: type_name::<T>(),
400            mutable: true,
401        })
402    }
403    fn matches(world: &World, entity: Entity) -> bool {
404        world.has::<T>(entity)
405    }
406    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error> {
407        world.validate(entity)?;
408        world
409            .cell::<T>(entity.index as usize)
410            .ok_or(Error::MissingComponent(type_name::<T>()))?
411            .try_borrow_mut()
412            .map_err(|_| Error::BorrowConflict(type_name::<T>()))
413    }
414}
415impl sealed::Access for Entity {}
416impl Access for Entity {
417    type Item<'world> = Entity;
418    fn info() -> Option<AccessInfo> {
419        None
420    }
421    fn matches(world: &World, entity: Entity) -> bool {
422        world.contains(entity)
423    }
424    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error> {
425        world.validate(entity)?;
426        Ok(entity)
427    }
428}
429
430/// Query signature: one access type or a tuple containing up to four access types.
431pub trait Query: sealed::Query {
432    type Item<'world>;
433    #[doc(hidden)]
434    fn validate() -> Result<(), Error>;
435    #[doc(hidden)]
436    fn matches(world: &World, entity: Entity) -> bool;
437    #[doc(hidden)]
438    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error>;
439}
440
441impl<A: Access> sealed::Query for A {}
442impl<A: Access> Query for A {
443    type Item<'world> = A::Item<'world>;
444    fn validate() -> Result<(), Error> {
445        Ok(())
446    }
447    fn matches(world: &World, entity: Entity) -> bool {
448        A::matches(world, entity)
449    }
450    fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error> {
451        A::fetch(world, entity)
452    }
453}
454
455macro_rules! query_tuple {
456    ($($name:ident),+) => {
457        impl<$($name: Access),+> sealed::Query for ($($name,)+) {}
458        impl<$($name: Access),+> Query for ($($name,)+) {
459            type Item<'world> = ($($name::Item<'world>,)+);
460            fn validate() -> Result<(), Error> {
461                let access = [$($name::info(),)+];
462                for (index, item) in access.iter().enumerate() {
463                    if let Some(item) = item {
464                        if access[..index].iter().flatten().any(|other| {
465                            other.id == item.id && (other.mutable || item.mutable)
466                        }) { return Err(Error::ConflictingQuery(item.name)); }
467                    }
468                }
469                Ok(())
470            }
471            fn matches(world: &World, entity: Entity) -> bool {
472                $($name::matches(world, entity))&&+
473            }
474            fn fetch(world: &World, entity: Entity) -> Result<Self::Item<'_>, Error> {
475                Ok(($($name::fetch(world, entity)?,)+))
476            }
477        }
478    }
479}
480query_tuple!(A);
481query_tuple!(A, B);
482query_tuple!(A, B, C);
483query_tuple!(A, B, C, D);
484
485pub struct QueryBorrow<'world, Q: Query> {
486    world: &'world World,
487    marker: PhantomData<fn() -> Q>,
488}
489
490impl<'world, Q: Query> QueryBorrow<'world, Q> {
491    pub fn iter(&self) -> QueryIter<'world, Q> {
492        QueryIter {
493            inner: self.try_iter(),
494        }
495    }
496    /// Visits all matching entities, reporting borrow conflicts without skipping silently.
497    /// A conflict releases any partial tuple borrows and does not stop later entities.
498    pub fn try_iter(&self) -> TryQueryIter<'world, Q> {
499        TryQueryIter {
500            world: self.world,
501            cursor: 0,
502            marker: PhantomData,
503        }
504    }
505}
506impl<'world, Q: Query> IntoIterator for &mut QueryBorrow<'world, Q> {
507    type Item = Q::Item<'world>;
508    type IntoIter = QueryIter<'world, Q>;
509    fn into_iter(self) -> Self::IntoIter {
510        self.iter()
511    }
512}
513impl<'world, Q: Query> IntoIterator for &QueryBorrow<'world, Q> {
514    type Item = Q::Item<'world>;
515    type IntoIter = QueryIter<'world, Q>;
516    fn into_iter(self) -> Self::IntoIter {
517        self.iter()
518    }
519}
520impl<'world, Q: Query> IntoIterator for QueryBorrow<'world, Q> {
521    type Item = Q::Item<'world>;
522    type IntoIter = QueryIter<'world, Q>;
523    fn into_iter(self) -> Self::IntoIter {
524        self.iter()
525    }
526}
527
528pub struct TryQueryIter<'world, Q: Query> {
529    world: &'world World,
530    cursor: usize,
531    marker: PhantomData<fn() -> Q>,
532}
533impl<'world, Q: Query> Iterator for TryQueryIter<'world, Q> {
534    type Item = Result<Q::Item<'world>, Error>;
535    fn next(&mut self) -> Option<Self::Item> {
536        while let Some(slot) = self.world.slots.get(self.cursor) {
537            let entity = Entity {
538                index: self.cursor as u32,
539                generation: slot.generation,
540            };
541            self.cursor += 1;
542            // Match the entire tuple before borrowing any member.
543            if slot.alive && Q::matches(self.world, entity) {
544                return Some(Q::fetch(self.world, entity));
545            }
546        }
547        None
548    }
549    fn size_hint(&self) -> (usize, Option<usize>) {
550        (0, Some(self.world.slots.len().saturating_sub(self.cursor)))
551    }
552}
553impl<Q: Query> std::iter::FusedIterator for TryQueryIter<'_, Q> {}
554
555pub struct QueryIter<'world, Q: Query> {
556    inner: TryQueryIter<'world, Q>,
557}
558impl<'world, Q: Query> Iterator for QueryIter<'world, Q> {
559    type Item = Q::Item<'world>;
560    fn next(&mut self) -> Option<Self::Item> {
561        self.inner
562            .next()
563            .map(|item| item.unwrap_or_else(|error| panic!("{error}")))
564    }
565    fn size_hint(&self) -> (usize, Option<usize>) {
566        self.inner.size_hint()
567    }
568}
569impl<Q: Query> std::iter::FusedIterator for QueryIter<'_, Q> {}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574    use std::cell::Cell;
575    use std::rc::Rc;
576
577    #[derive(Debug, PartialEq)]
578    struct Position(f32, f32);
579    #[derive(Debug, PartialEq)]
580    struct Velocity(f32, f32);
581    #[derive(Debug, PartialEq)]
582    struct Health(i32);
583
584    #[test]
585    fn reused_slot_rejects_every_stale_handle_operation() {
586        let mut world = World::new();
587        let old = world.spawn((Position(1.0, 2.0), Health(10)));
588        world.despawn(old).unwrap();
589        let new = world.spawn((Position(8.0, 9.0),));
590        assert_eq!(old.id(), new.id());
591        assert_ne!(old.generation(), new.generation());
592        assert!(!world.contains(old));
593        assert!(world.contains(new));
594        assert_eq!(Entity::from_bits(new.to_bits()), new);
595        assert!(matches!(
596            world.get::<&Position>(old),
597            Err(Error::NoSuchEntity(_))
598        ));
599        assert!(matches!(
600            world.get_mut::<Position>(old),
601            Err(Error::NoSuchEntity(_))
602        ));
603        assert!(matches!(
604            world.insert(old, (Health(50),)),
605            Err(Error::NoSuchEntity(_))
606        ));
607        assert!(matches!(
608            world.remove::<Position>(old),
609            Err(Error::NoSuchEntity(_))
610        ));
611        assert!(matches!(world.despawn(old), Err(Error::NoSuchEntity(_))));
612        assert!(!world.has::<Health>(new));
613        assert_eq!(*world.get::<&Position>(new).unwrap(), Position(8.0, 9.0));
614        assert_eq!(world.len(), 1);
615    }
616
617    #[test]
618    fn exhausted_generations_retire_slots_instead_of_wrapping() {
619        let mut world = World::new();
620        let first = world.spawn((7_i32,));
621        world.slots[first.id() as usize].generation = u32::MAX;
622        let final_handle = Entity {
623            index: first.id(),
624            generation: u32::MAX,
625        };
626        world.despawn(final_handle).unwrap();
627        let next = world.spawn((8_i32,));
628        assert_ne!(next.id(), first.id());
629        assert!(!world.contains(first));
630        assert!(!world.contains(final_handle));
631    }
632
633    #[test]
634    fn tuple_queries_update_only_matching_entities_and_return_ids() {
635        let mut world = World::new();
636        let matching = world.spawn((Position(2.0, 3.0), Velocity(4.0, -1.0), Health(30), "pilot"));
637        let stationary = world.spawn((Position(20.0, 30.0), Health(100)));
638        let removed = world.spawn((Position(9.0, 9.0), Velocity(1.0, 1.0)));
639        world.despawn(removed).unwrap();
640        for (entity, mut position, mut velocity) in
641            &mut world.query::<(Entity, &mut Position, &mut Velocity)>()
642        {
643            assert_eq!(entity, matching);
644            position.0 += velocity.0;
645            position.1 += velocity.1;
646            velocity.0 *= 0.5;
647        }
648        assert_eq!(
649            *world.get::<&Position>(matching).unwrap(),
650            Position(6.0, 2.0)
651        );
652        assert_eq!(
653            *world.get::<&Velocity>(matching).unwrap(),
654            Velocity(2.0, -1.0)
655        );
656        assert_eq!(
657            *world.get::<&Position>(stationary).unwrap(),
658            Position(20.0, 30.0)
659        );
660        assert_eq!(
661            world
662                .query::<(&Position, &Velocity, &Health, &&str)>()
663                .iter()
664                .count(),
665            1
666        );
667        assert_eq!(world.query::<&Position>().iter().count(), 2);
668        assert_eq!(
669            world.entities().collect::<Vec<_>>(),
670            vec![matching, stationary]
671        );
672    }
673
674    #[test]
675    fn guards_can_outlive_iterators_and_other_entity_guards() {
676        let mut world = World::new();
677        let a = world.spawn((Health(1),));
678        let b = world.spawn((Health(2),));
679        let (mut first, mut second) = {
680            let mut items = world.query::<&mut Health>().iter();
681            let first = items.next().unwrap();
682            let second = items.next().unwrap();
683            assert!(items.next().is_none());
684            (first, second)
685        };
686        first.0 += 10;
687        second.0 += 20;
688        assert!(matches!(
689            world.get::<&Health>(a),
690            Err(Error::BorrowConflict(_))
691        ));
692        drop(first);
693        assert_eq!(world.get::<&Health>(a).unwrap().0, 11);
694        assert!(matches!(
695            world.get::<&Health>(b),
696            Err(Error::BorrowConflict(_))
697        ));
698        drop(second);
699        assert_eq!(world.get::<&Health>(b).unwrap().0, 22);
700    }
701
702    #[test]
703    fn borrow_conflicts_are_fallible_and_release_on_drop() {
704        let mut world = World::new();
705        let entity = world.spawn((Health(7), Position(2.0, 3.0)));
706        let shared = world.get::<&Health>(entity).unwrap();
707        let other_shared = world.get::<&Health>(entity).unwrap();
708        assert!(matches!(
709            world.get::<&mut Health>(entity),
710            Err(Error::BorrowConflict(_))
711        ));
712        assert!(world.get_mut::<Position>(entity).is_ok());
713        drop((shared, other_shared));
714        let mut exclusive = world.get_mut::<Health>(entity).unwrap();
715        exclusive.0 = 9;
716        assert!(matches!(
717            world.get::<&Health>(entity),
718            Err(Error::BorrowConflict(_))
719        ));
720        assert!(matches!(
721            world.get_mut::<Health>(entity),
722            Err(Error::BorrowConflict(_))
723        ));
724        drop(exclusive);
725        assert_eq!(world.get::<&Health>(entity).unwrap().0, 9);
726    }
727
728    #[test]
729    fn query_aliases_reject_mutable_duplicates_allow_shared_duplicates() {
730        let mut world = World::new();
731        world.spawn((Health(10),));
732        assert!(matches!(
733            world.try_query::<(&mut Health, &Health)>(),
734            Err(Error::ConflictingQuery(_))
735        ));
736        assert!(matches!(
737            world.try_query::<(&mut Health, &mut Health)>(),
738            Err(Error::ConflictingQuery(_))
739        ));
740        let values = world.query::<(&Health, &Health)>().iter().next().unwrap();
741        assert_eq!(values.0.0, values.1.0);
742        assert_eq!(world.query::<(Entity, Entity)>().iter().count(), 1);
743    }
744
745    #[test]
746    fn tuple_borrow_failure_releases_partial_borrows_and_continues() {
747        let mut world = World::new();
748        let first = world.spawn((Health(1), Position(1.0, 1.0)));
749        let second = world.spawn((Health(2), Position(2.0, 2.0)));
750        let lock = world.get_mut::<Position>(first).unwrap();
751        let mut iterator = world.query::<(&mut Health, &mut Position)>().try_iter();
752        assert!(matches!(
753            iterator.next().unwrap(),
754            Err(Error::BorrowConflict(_))
755        ));
756        // Fetching Health succeeded before Position failed, so it must have been released.
757        assert!(world.get_mut::<Health>(first).is_ok());
758        let (mut health, _) = iterator.next().unwrap().unwrap();
759        health.0 = 12;
760        drop((iterator, health, lock));
761        assert_eq!(world.get::<&Health>(second).unwrap().0, 12);
762    }
763
764    #[test]
765    fn missing_components_are_filtered_before_any_borrow_attempt() {
766        let mut world = World::new();
767        let entity = world.spawn((Health(10),));
768        let guard = world.get_mut::<Health>(entity).unwrap();
769        let mut query = world.query::<(&mut Health, &Position)>().try_iter();
770        assert!(query.next().is_none());
771        drop(guard);
772    }
773
774    #[test]
775    fn duplicate_bundles_are_rejected_before_mutation() {
776        let mut world = World::new();
777        assert!(matches!(
778            world.try_spawn((1_i32, 2_i32)),
779            Err(Error::DuplicateComponent(_))
780        ));
781        assert!(world.is_empty());
782        let entity = world.spawn((Health(10), 5_i32));
783        assert!(matches!(
784            world.insert(entity, (6_i32, 7_i32)),
785            Err(Error::DuplicateComponent(_))
786        ));
787        assert_eq!(*world.get::<&i32>(entity).unwrap(), 5);
788        assert_eq!(world.len(), 1);
789    }
790
791    #[test]
792    fn insert_replace_remove_and_empty_entities_work() {
793        let mut world = World::new();
794        let entity = world.spawn(());
795        assert!(world.contains(entity));
796        world
797            .insert(entity, (Health(10), Position(1.0, 2.0)))
798            .unwrap();
799        world.insert(entity, (Health(25),)).unwrap();
800        assert_eq!(world.remove::<Health>(entity).unwrap(), Health(25));
801        assert!(matches!(
802            world.remove::<Health>(entity),
803            Err(Error::MissingComponent(_))
804        ));
805        assert!(matches!(
806            world.get::<&Health>(entity),
807            Err(Error::MissingComponent(_))
808        ));
809        assert_eq!(world.query::<&Position>().iter().count(), 1);
810        world.despawn(entity).unwrap();
811        assert!(world.is_empty());
812    }
813
814    #[test]
815    fn components_drop_exactly_once_when_replaced_removed_or_despawned() {
816        struct CountDrop(Rc<Cell<usize>>);
817        impl Drop for CountDrop {
818            fn drop(&mut self) {
819                self.0.set(self.0.get() + 1);
820            }
821        }
822        let drops = Rc::new(Cell::new(0));
823        let mut world = World::new();
824        let entity = world.spawn((CountDrop(drops.clone()),));
825        world.insert(entity, (CountDrop(drops.clone()),)).unwrap();
826        assert_eq!(drops.get(), 1);
827        let removed = world.remove::<CountDrop>(entity).unwrap();
828        assert_eq!(drops.get(), 1);
829        drop(removed);
830        assert_eq!(drops.get(), 2);
831        world.insert(entity, (CountDrop(drops.clone()),)).unwrap();
832        world.despawn(entity).unwrap();
833        assert_eq!(drops.get(), 3);
834        drop(world);
835        assert_eq!(drops.get(), 3);
836    }
837
838    #[test]
839    fn independently_overlapping_iterators_conflict_without_aliasing() {
840        let mut world = World::new();
841        world.spawn((Health(10),));
842        let mut first = world.query::<&mut Health>().iter();
843        let guard = first.next().unwrap();
844        let mut second = world.query::<&mut Health>().try_iter();
845        assert!(matches!(
846            second.next().unwrap(),
847            Err(Error::BorrowConflict(_))
848        ));
849        drop(guard);
850        assert_eq!(world.query::<&Health>().iter().next().unwrap().0, 10);
851    }
852
853    #[test]
854    fn failed_panicking_query_does_not_poison_component_borrows() {
855        let mut world = World::new();
856        let entity = world.spawn((Health(10), Position(1.0, 2.0)));
857        let lock = world.get_mut::<Position>(entity).unwrap();
858        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
859            let _ = world.query::<(&mut Health, &mut Position)>().iter().next();
860        }));
861        assert!(result.is_err());
862        assert!(world.get_mut::<Health>(entity).is_ok());
863        drop(lock);
864        assert!(world.get_mut::<Position>(entity).is_ok());
865    }
866
867    #[test]
868    fn thousands_of_reuse_cycles_preserve_counts_and_components() {
869        let mut world = World::new();
870        let mut stale = Vec::new();
871        for i in 0_u32..2_000 {
872            let entity = world.spawn((i,));
873            assert_eq!(world.len(), 1);
874            assert_eq!(*world.get::<&u32>(entity).unwrap(), i);
875            assert!(stale.iter().all(|old| !world.contains(*old)));
876            world.despawn(entity).unwrap();
877            stale.push(entity);
878            assert!(world.is_empty());
879        }
880        assert_eq!(world.slots.len(), 1);
881    }
882    #[test]
883    fn panicking_destructor_does_not_recycle_partially_cleaned_slot() {
884        struct PanicOnDrop;
885        impl Drop for PanicOnDrop {
886            fn drop(&mut self) {
887                panic!("component destructor failed");
888            }
889        }
890        let mut world = World::new();
891        let entity = world.spawn((PanicOnDrop, Health(10)));
892        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
893            world.despawn(entity).unwrap();
894        }));
895        assert!(result.is_err());
896        assert!(!world.contains(entity));
897        assert!(world.is_empty());
898        let next = world.spawn((Position(1.0, 2.0),));
899        assert_ne!(next.id(), entity.id());
900        assert!(!world.has::<Health>(next));
901    }
902}