latticeon 0.1.0

A math and ECS library focused on easy academic reproduction of animation, physics simulation, and AI
Documentation
//! Component trait and component type identification.

use std::any::TypeId;
use std::sync::atomic::{AtomicU32, Ordering};

/// Marker trait for types that can be used as components.
///
/// Implement this by hand or use the derive macro:
///
/// ```ignore
/// use latticeon::ecs::prelude::*;
///
/// #[derive(Component)]
/// struct Position(f32, f32);
/// ```
///
/// Components must be `Send + Sync` for multi-threaded ECS.
pub trait Component: Send + Sync + 'static {}

/// Runtime metadata for a registered component type.
#[derive(Clone, Copy)]
pub struct ComponentMeta {
    pub size: usize,
    pub align: usize,
    /// If `Some`, the function drops one element at the given pointer.
    pub drop_fn: Option<unsafe fn(*mut u8)>,
}

impl ComponentMeta {
    pub fn of<T: 'static>() -> Self {
        let drop_fn: Option<unsafe fn(*mut u8)> = if std::mem::needs_drop::<T>() {
            Some(|ptr: *mut u8| unsafe { std::ptr::drop_in_place(ptr as *mut T) })
        } else {
            None
        };
        Self {
            size: std::mem::size_of::<T>(),
            align: std::mem::align_of::<T>(),
            drop_fn,
        }
    }
}

/// Assigns a stable component id per type. Also stores `ComponentMeta` for each registered type.
pub struct ComponentIdRegistry {
    next: AtomicU32,
    mapping: std::sync::RwLock<std::collections::HashMap<TypeId, u32>>,
    /// Indexed by ComponentId.0. Grows when new types are registered.
    metas: std::sync::RwLock<Vec<ComponentMeta>>,
}

impl ComponentIdRegistry {
    pub fn new() -> Self {
        Self {
            next: AtomicU32::new(0),
            mapping: std::sync::RwLock::new(std::collections::HashMap::new()),
            metas: std::sync::RwLock::new(Vec::new()),
        }
    }

    /// Get or assign a component id for the given type. Also registers its metadata.
    pub fn id_for<T: Component>(&self) -> ComponentId {
        let tid = TypeId::of::<T>();
        {
            let map = self.mapping.read().expect("component registry lock");
            if let Some(&id) = map.get(&tid) {
                return ComponentId(id);
            }
        }
        let id = self.next.fetch_add(1, Ordering::Relaxed);
        self.mapping
            .write()
            .expect("component registry lock")
            .insert(tid, id);
        let meta = ComponentMeta::of::<T>();
        let mut metas = self.metas.write().expect("component metas lock");
        if (id as usize) >= metas.len() {
            metas.resize(id as usize + 1, ComponentMeta { size: 0, align: 1, drop_fn: None });
        }
        metas[id as usize] = meta;
        ComponentId(id)
    }

    /// Get component id by TypeId if already registered.
    pub fn get_by_type_id(&self, type_id: TypeId) -> Option<ComponentId> {
        self.mapping
            .read()
            .expect("component registry lock")
            .get(&type_id)
            .map(|&id| ComponentId(id))
    }

    /// Get the component id for T if already registered (does not auto-assign).
    pub fn get<T: Component>(&self) -> Option<ComponentId> {
        self.get_by_type_id(TypeId::of::<T>())
    }

    /// Get metadata for a registered component id.
    pub fn meta(&self, id: ComponentId) -> Option<ComponentMeta> {
        self.metas
            .read()
            .expect("component metas lock")
            .get(id.0 as usize)
            .copied()
    }
}

impl Default for ComponentIdRegistry {
    fn default() -> Self {
        Self::new()
    }
}

unsafe impl Send for ComponentIdRegistry {}
unsafe impl Sync for ComponentIdRegistry {}

/// Opaque id for a component type. Stable for the lifetime of the registry.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ComponentId(pub u32);

impl std::fmt::Debug for ComponentId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "ComponentId({})", self.0)
    }
}