gizmo-core 0.9.1

A custom ECS and physics engine aimed for realistic simulations.
Documentation
use super::sealed;
use crate::archetype::Archetype;
use crate::world::World;
use std::any::TypeId;

// =========================================================================
// FETCH COMPONENT TRAIT
// =========================================================================

/// The per-component half of the query DSL: how ONE component type is located in storage and
/// turned into an item.
///
/// Implemented only for `&T` (shared) and [`Mut<T>`](Mut) (exclusive, change-tracked). Sealed —
/// a hand-written impl could hand out two `&mut T` for the same row, so this is not an extension
/// point. Every `FetchComponent` becomes a [`WorldQuery`](super::WorldQuery) through a blanket
/// impl, which is why `&T` and `Mut<T>` can be used directly as query operands.
///
/// Both impls cover the two storage kinds behind one interface, and the difference leaks into
/// the parameters of nearly every method: for `StorageType::Table` the fetch is a base pointer
/// and `row` selects the element, whereas for `StorageType::SparseSet` the fetch is the address
/// of the world's sparse set, `row` is ignored, and the lookup goes through `entity_id`.
pub trait FetchComponent: sealed::SealedFetch {
    /// The component type actually being accessed — `T` for both `&T` and `Mut<T>`. This is the
    /// identity used for aliasing detection and for the scheduler's access set, which is why
    /// `&T` and `Mut<T>` collide with each other exactly as they must.
    type Component: 'static;
    /// Per-archetype resolved access for this one component, produced by
    /// [`fetch_raw`](FetchComponent::fetch_raw). Concretely it is the archetype column's base
    /// pointer for `Table` storage, or the address of the world's `ComponentSparseSet` for
    /// `SparseSet` storage — `Mut<T>` carries the column's `ComponentTicks` pointer and the
    /// system tick alongside. Which of the two it is stays encoded in the value, so
    /// `get_item`/`contains_entity`/`get_slice` branch on the fetch itself instead of
    /// re-reading `T::storage_type()` per row.
    ///
    /// The `Copy` bound and the validity rules are those of
    /// [`WorldQuery::Fetch`](super::WorldQuery::Fetch) and apply unchanged here.
    type Fetch<'w>: Copy; // Raw pointers are Copy
    /// One row's worth of access: `&'w T` for `&T`, [`Mut<'w, T>`](Mut) for `Mut<T>`. Obtaining
    /// a `Mut` does not by itself mark the row as changed — only writing through its `DerefMut`
    /// does.
    type Item<'w>;
    /// One whole archetype's worth of contiguous access, for chunk iteration: `&'w [T]` for
    /// `&T`, `&'w mut [T]` for `Mut<T>`. The `Mut<T>` slice is only handed out after
    /// [`get_slice`](FetchComponent::get_slice) has stamped the current tick onto all `len`
    /// rows — a raw `&mut [T]` cannot report which elements were written, so every row in it
    /// counts as changed whether you touch it or not.
    ///
    /// See [`WorldQuery::Slice`](super::WorldQuery::Slice) for why a `SparseSet` component has
    /// no slice form at all.
    type Slice<'w>;

    /// `true` for `Mut<T>`, `false` for `&T`.
    ///
    /// Supplies the mutability half of the `(TypeId, is_mut)` pair fed to the query aliasing
    /// check: two operands naming the same [`Component`](FetchComponent::Component) panic at
    /// query construction unless both are `false`. The same flag decides whether a system is
    /// recorded as a reader or a writer of this component for parallel scheduling.
    const IS_MUT: bool;

    /// Prepares a raw pointer fetch on a per-archetype basis.
    ///
    /// # Safety
    /// The archetype must be valid and the returned fetch pointer must stay valid for the whole lifetime of the archetype.
    unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, system_tick: u32) -> Option<Self::Fetch<'w>>;

    /// Fetches the data from the raw pointer.
    ///
    /// # Safety
    /// The `row` value must be smaller than the archetype's element count.
    unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w>;

    /// Fetches contiguous memory as a chunk, in Slice form (SIMD).
    ///
    /// # Safety
    /// The `len` value must not exceed the archetype's element count.
    unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w>;

    /// Returns whether `entity_id` really carries this component.
    ///
    /// In `Table` storage this is ALWAYS `true`: `matches_archetype` has already restricted
    /// iteration to the archetypes that contain the component. In `SparseSet` storage, however,
    /// `matches_archetype` is deliberately WIDE (it returns `true` for every archetype),
    /// which is why the per-row presence check must be done HERE — otherwise `get_item`
    /// indexes the sparse set out of bounds for entities that do NOT have the component (a
    /// panic reachable from safe code or — on a tombstone slot — UB in a release build).
    ///
    /// # Safety
    /// `fetch` must come from `fetch_raw` for the world being iterated.
    unsafe fn contains_entity<'w>(fetch: Self::Fetch<'w>, entity_id: u32) -> bool {
        let _ = (fetch, entity_id);
        true
    }
}

impl<T: crate::component::Component> sealed::SealedFetch for &T {}
impl<T: crate::component::Component> FetchComponent for &T {
    type Component = T;
    type Fetch<'w> = (*const u8, Option<*const crate::archetype::sparse_set::ComponentSparseSet>);
    type Item<'w> = &'w T;
    type Slice<'w> = &'w [T];
    const IS_MUT: bool = false;

    unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, _system_tick: u32) -> Option<Self::Fetch<'w>> {
        if T::storage_type() == crate::component::StorageType::SparseSet {
            let set = world.sparse_sets.get(&TypeId::of::<T>())?;
            Some((std::ptr::null(), Some(set as *const _)))
        } else {
            let col = arch.get_column(TypeId::of::<T>())?;
            Some((col.data_ptr(), None))
        }
    }

    unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
        if let Some(set_ptr) = fetch.1 {
            let set = &*set_ptr;
            let ptr = set.get_ptr(entity_id).unwrap() as *const T;
            &*ptr
        } else {
            let ptr = fetch.0.add(row * std::mem::size_of::<T>()) as *const T;
            &*ptr
        }
    }

    unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
        if fetch.1.is_some() {
            panic!("Cannot use iter_chunks with SparseSet components");
        }
        std::slice::from_raw_parts(fetch.0 as *const T, len)
    }

    unsafe fn contains_entity<'w>(fetch: Self::Fetch<'w>, entity_id: u32) -> bool {
        match fetch.1 {
            Some(set_ptr) => (*set_ptr).contains(entity_id),
            None => true,
        }
    }
}

/// Change-tracked (`Changed<T>`) mutable access to a component.
///
/// **Aliasing:** `world.query::<Mut<T>>()` / [`World::borrow_mut`](crate::world::World::borrow_mut)
/// hands out `&mut T` from `&self`; two *live* `Mut` queries for the same `T` (or one `Mut`
/// together with a `&T`) at the same time are UB. For the caller contract and for safe
/// alternatives see the aliasing section of [`World::query`](crate::world::World::query).
pub struct Mut<'a, T: 'static> {
    value: &'a mut T,
    ticks: &'a mut crate::archetype::ComponentTicks,
    current_tick: u32,
}

impl<T> std::ops::Deref for Mut<'_, T> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &T {
        self.value
    }
}

impl<T> std::ops::DerefMut for Mut<'_, T> {
    #[inline]
    fn deref_mut(&mut self) -> &mut T {
        self.ticks.changed = self.current_tick;
        self.value
    }
}

impl<'a, T> Mut<'a, T> {
    /// Mutable access that does **not** stamp the change tick, unlike `DerefMut`, which sets
    /// `ticks.changed` to the current system tick on every single use.
    ///
    /// A write made through this handle stays invisible to [`Changed<T>`](super::Changed) until
    /// something else marks the component. Reserve it for genuinely incidental writes — caches,
    /// scratch fields, restoring a value you just read; using it for a real state change
    /// silently starves every system that reacts to `Changed<T>`.
    ///
    /// It only suppresses; it cannot un-mark a change already recorded this frame, and it has no
    /// bearing on [`Added<T>`](super::Added), which reads a separate tick that changes only when
    /// the row is (re)initialised.
    #[inline]
    pub fn bypass_change_detection(&mut self) -> &mut T {
        self.value
    }
}

impl<T: crate::component::Component> sealed::SealedFetch for Mut<'_, T> {}
impl<T: crate::component::Component> FetchComponent for Mut<'_, T> {
    type Component = T;
    type Fetch<'w> = (*mut u8, *mut crate::archetype::ComponentTicks, u32, Option<*mut crate::archetype::sparse_set::ComponentSparseSet>);
    type Item<'w> = Mut<'w, T>;
    type Slice<'w> = &'w mut [T];
    const IS_MUT: bool = true;

    unsafe fn fetch_raw<'w>(world: &'w World, arch: &Archetype, system_tick: u32) -> Option<Self::Fetch<'w>> {
        if T::storage_type() == crate::component::StorageType::SparseSet {
            // SHARED lookup — NOT `&World -> &mut World`. Casting `&World` to
            // `*mut World` to call `HashMap::get_mut` was aliasing UB (retag from
            // SharedReadOnly to a mutable permission), reachable from 100% safe
            // code via `query_mut::<Mut<Sparse>>().iter_mut()`. We only need the
            // set's address; `get_item` reaches its elements through a shared ref +
            // interior mutability (BlobVec / `UnsafeCell<ComponentTicks>`), so no
            // `&mut ComponentSparseSet` is ever formed (that would race under
            // `par_for_each_mut`).
            let set = world.sparse_sets.get(&TypeId::of::<T>())?;
            Some((std::ptr::null_mut(), std::ptr::null_mut(), system_tick, Some(set as *const _ as *mut _)))
        } else {
            let col = arch.get_column_mut(TypeId::of::<T>())?;
            Some((col.data_ptr_mut(), col.ticks_ptr_mut(), system_tick, None))
        }
    }

    unsafe fn get_item<'w>(fetch: Self::Fetch<'w>, row: usize, entity_id: u32) -> Self::Item<'w> {
        let (data_ptr, ticks_ptr, system_tick, set_opt) = fetch;
        if let Some(set_ptr) = set_opt {
            // SHARED `&*set_ptr` (not `&mut`). `par_for_each_mut` runs archetype/row tasks in
            // parallel; every entity lives in exactly one dense row, so the rows written are
            // disjoint across tasks. Forming an exclusive `&mut *set_ptr` per task would be
            // instant aliasing UB (many live `&mut ComponentSparseSet`) — a real data race
            // reachable from 100% safe code (`query_mut::<Mut<Sparse>>().par_for_each_mut`).
            // BlobVec::get_unchecked_mut takes `&self` (interior mutability) and Vec::as_ptr
            // gives the ticks base, so we reach the disjoint element through a shared ref only.
            let set = &*set_ptr;
            let e = entity_id as usize;
            let dense_row = set.sparse[e] as usize;
            let ptr = set.dense.get_unchecked_mut(dense_row) as *mut T;
            // `ticks` is `Vec<UnsafeCell<ComponentTicks>>`; `UnsafeCell::get` yields a
            // write-provenance `*mut` through the shared `&set`, so mutating disjoint
            // rows from parallel tasks is sound (a raw `Vec::as_ptr` would not be).
            let ticks_ptr = (*set.ticks.as_ptr().add(dense_row)).get();
            Mut {
                value: &mut *ptr,
                ticks: &mut *ticks_ptr,
                current_tick: system_tick,
            }
        } else {
            let ptr = data_ptr.add(row * std::mem::size_of::<T>()) as *mut T;
            Mut {
                value: &mut *ptr,
                ticks: &mut *ticks_ptr.add(row),
                current_tick: system_tick,
            }
        }
    }

    unsafe fn contains_entity<'w>(fetch: Self::Fetch<'w>, entity_id: u32) -> bool {
        match fetch.3 {
            Some(set_ptr) => (*set_ptr).contains(entity_id),
            None => true,
        }
    }

    unsafe fn get_slice<'w>(fetch: Self::Fetch<'w>, len: usize) -> Self::Slice<'w> {
        let (data_ptr, ticks_ptr, system_tick, set_opt) = fetch;
        if set_opt.is_some() {
            panic!("Cannot use iter_chunks with SparseSet components");
        }
        // Temkinli (conservative) işaretleme: ham `&mut [T]` dilimde hangi elemanın
        // yazıldığı izlenemediğinden verilen tüm satırlar "changed" işaretlenir. Bu,
        // gerçek bir yazmayı asla kaçırmaz (güvenli); detay için bkz. `iter_chunks_mut`.
        let ticks = std::slice::from_raw_parts_mut(ticks_ptr, len);
        for tick in ticks.iter_mut() {
            tick.changed = system_tick;
        }
        std::slice::from_raw_parts_mut(data_ptr as *mut T, len)
    }
}