gizmo-core 0.9.1

A custom ECS and physics engine aimed for realistic simulations.
Documentation
//! Entity identity: the id/generation handle and the allocator that recycles it.
//!
//! An [`Entity`] is an id plus a generation counter. Reusing an id after a despawn bumps the
//! generation, so a stale handle compares unequal to the live entity occupying the same slot
//! instead of silently addressing it — the whole reason the generation exists.
//!
//! Anything that stores a bare `u32` id rather than an `Entity` gives that protection up.
/// The ECS Entity identifier — a packed u64 representation.
///
/// The low 32 bits = the entity ID (slot index), the high 32 bits = the generation (reuse counter).
/// The generation is incremented when a new entity is assigned to the same slot (ID), and it makes
/// it possible to detect old references (dangling entities) safely.
///
/// # Layout
/// ```text
/// ┌──────────────────────────────────────────────────────────────────┐
/// │  63 ───────────── 32 │ 31 ───────────── 0 │
/// │     generation (u32) │       id (u32)     │
/// └──────────────────────────────────────────────────────────────────┘
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Entity(u64);

impl Entity {
    /// The invalid / null entity sentinel value.
    /// It can be used instead of `Option<Entity>` (ergonomic and cache friendly).
    pub const INVALID: Self = Self(u64::MAX);

    /// Builds an `Entity` handle from an explicit `id` + `generation`.
    ///
    /// FOOTGUN: if you only have a raw `id` (e.g. from the UI, a script, or a saved
    /// reference) do NOT fabricate `Entity::new(id, 0)` to call a generation-checked API
    /// (`World::is_alive`/`entity_component_types`/`get_entity`, queries): once the id slot
    /// has been recycled (despawn→spawn bumps the generation) that gen-0 handle is stale
    /// and silently misses / mis-targets. Use [`World::entity(id)`](crate::World::entity),
    /// which reconstructs the CURRENT generation. `new` is for deserialization and tests
    /// where the generation is known, or for purely id-keyed internal addressing.
    #[inline]
    pub fn new(id: u32, generation: u32) -> Self {
        Self(((generation as u64) << 32) | id as u64)
    }

    /// Returns the Entity's slot index (ID).
    #[inline]
    pub fn id(self) -> u32 {
        self.0 as u32
    }

    /// Returns the Entity's generation counter.
    #[inline]
    pub fn generation(self) -> u32 {
        (self.0 >> 32) as u32
    }

    /// Checks whether this entity is valid (i.e. not INVALID).
    #[inline]
    pub fn is_valid(self) -> bool {
        self != Self::INVALID
    }

    /// Converts the Entity into its raw u64 bit representation.
    /// It can be used for serialization, network sync and as a hash key.
    #[inline]
    pub fn to_bits(self) -> u64 {
        self.0
    }

    /// Creates an Entity from a raw u64 bit representation.
    #[inline]
    pub fn from_bits(bits: u64) -> Self {
        Self(bits)
    }
}

impl std::fmt::Display for Entity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if *self == Self::INVALID {
            write!(f, "Entity(INVALID)")
        } else {
            write!(f, "Entity({}:{})", self.id(), self.generation())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_and_accessors() {
        let e = Entity::new(42, 7);
        assert_eq!(e.id(), 42);
        assert_eq!(e.generation(), 7);
    }

    #[test]
    fn test_zero_generation() {
        let e = Entity::new(0, 0);
        assert_eq!(e.id(), 0);
        assert_eq!(e.generation(), 0);
        assert!(e.is_valid());
    }

    #[test]
    fn test_max_values() {
        let e = Entity::new(u32::MAX - 1, u32::MAX - 1);
        assert_eq!(e.id(), u32::MAX - 1);
        assert_eq!(e.generation(), u32::MAX - 1);
        assert!(e.is_valid());
    }

    #[test]
    fn test_invalid_sentinel() {
        assert!(!Entity::INVALID.is_valid());
        assert_eq!(Entity::INVALID.id(), u32::MAX);
        assert_eq!(Entity::INVALID.generation(), u32::MAX);
    }

    #[test]
    fn test_to_bits_from_bits_roundtrip() {
        let e = Entity::new(123, 456);
        let bits = e.to_bits();
        let e2 = Entity::from_bits(bits);
        assert_eq!(e, e2);
        assert_eq!(e2.id(), 123);
        assert_eq!(e2.generation(), 456);
    }

    #[test]
    fn test_display() {
        let e = Entity::new(5, 2);
        assert_eq!(format!("{}", e), "Entity(5:2)");
    }

    #[test]
    fn test_display_invalid() {
        assert_eq!(format!("{}", Entity::INVALID), "Entity(INVALID)");
    }

    #[test]
    fn test_equality_and_hash() {
        use std::collections::HashSet;
        let e1 = Entity::new(1, 0);
        let e2 = Entity::new(1, 0);
        let e3 = Entity::new(1, 1); // Aynı ID, farklı generation

        assert_eq!(e1, e2);
        assert_ne!(e1, e3);

        let mut set = HashSet::new();
        set.insert(e1);
        assert!(set.contains(&e2));
        assert!(!set.contains(&e3));
    }

    #[test]
    fn test_copy_semantics() {
        let e1 = Entity::new(10, 3);
        let e2 = e1; // Copy
        assert_eq!(e1, e2);
        assert_eq!(e1.id(), e2.id());
    }

    #[test]
    fn test_serde_roundtrip() {
        let e = Entity::new(999, 42);
        let serialized = ron::to_string(&e).expect("serialize failed");
        let deserialized: Entity = ron::from_str(&serialized).expect("deserialize failed");
        assert_eq!(e, deserialized);
    }
}
/// Entity id allocation: hands out [`Entity`] handles, recycles ids through a free list and
/// keeps the per-slot generation counters that make a stale handle detectable.
pub mod allocator;