termite 0.1.0-alpha

A simple modern Entity Component System
Documentation
use std::{
    marker::PhantomData,
    ops::{Deref, DerefMut},
};

use crate::{
    Access, ChangeTicks, CommandQueue, Commands, ComponentId, Query, QueryState,
    ReadOnlyWorldQuery, Resource, SystemMeta, World, WorldQuery,
};

pub trait SystemParam: Sized {
    type Fetch: for<'w, 's> SystemParamFetch<'w, 's>;
}

pub type SystemParamItem<'w, 's, P> = <<P as SystemParam>::Fetch as SystemParamFetch<'w, 's>>::Item;

pub unsafe trait SystemParamState: Send + Sync + 'static {
    fn init(world: &mut World, meta: &mut SystemMeta) -> Self;

    #[inline]
    fn apply(&mut self, _world: &mut World) {}
}

pub trait SystemParamFetch<'w, 's>: SystemParamState {
    type Item: SystemParam<Fetch = Self>;

    unsafe fn get_param(
        &'s mut self,
        meta: &SystemMeta,
        world: &'w World,
        change_tick: u32,
    ) -> Self::Item;
}

unsafe impl SystemParamState for CommandQueue {
    fn init(_world: &mut World, _meta: &mut SystemMeta) -> Self {
        Self::default()
    }

    fn apply(&mut self, world: &mut World) {
        self.apply(world);
    }
}

impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue {
    type Item = Commands<'w, 's>;

    unsafe fn get_param(
        &'s mut self,
        _meta: &SystemMeta,
        world: &'w World,
        _change_tick: u32,
    ) -> Self::Item {
        Commands::new(self, world)
    }
}

impl<'w, 's> SystemParam for Commands<'w, 's> {
    type Fetch = CommandQueue;
}

unsafe impl<Q, F> SystemParamState for QueryState<Q, F>
where
    Q: WorldQuery + 'static,
    F: ReadOnlyWorldQuery + 'static,
{
    #[inline]
    fn init(world: &mut World, meta: &mut SystemMeta) -> Self {
        let state = QueryState::new(world);

        assert_access_compatibility(
            std::any::type_name::<Q>(),
            std::any::type_name::<F>(),
            state.filtered_access.access(),
            meta,
            world,
        );

        meta.access.extend(state.filtered_access.access());

        state
    }
}

impl<'w, 's, Q, F> SystemParamFetch<'w, 's> for QueryState<Q, F>
where
    Q: WorldQuery + 'static,
    F: ReadOnlyWorldQuery + 'static,
{
    type Item = Query<'w, 's, Q, F>;

    unsafe fn get_param(
        &'s mut self,
        meta: &SystemMeta,
        world: &'w World,
        change_tick: u32,
    ) -> Self::Item {
        unsafe { Query::new(world, self, meta.last_change_tick, change_tick) }
    }
}

impl<'w, 's, Q, F> SystemParam for Query<'w, 's, Q, F>
where
    Q: WorldQuery + 'static,
    F: ReadOnlyWorldQuery + 'static,
{
    type Fetch = QueryState<Q, F>;
}

fn assert_access_compatibility(
    query_type: &'static str,
    filter_type: &'static str,
    access: &Access<ComponentId>,
    meta: &SystemMeta,
    world: &World,
) {
    let conflicts = access.get_conflicts(&meta.access);

    if conflicts.is_empty() {
        return;
    }

    let messages = conflicts
        .into_iter()
        .map(|id| {
            let info = world.components.get(id).unwrap();
            info.name()
        })
        .collect::<Vec<_>>();

    let message = messages.join(", ");

    panic!(
        "Query<{}, {}> in system {} access component(s) {} that conflict with previous system parameters.",
        query_type,
        filter_type,
        meta.name(),
        message,
    );
}

pub struct Res<'w, T> {
    value: &'w T,
    ticks: &'w ChangeTicks,
    last_change_tick: u32,
    change_tick: u32,
}

impl<'w, T> Res<'w, T> {
    #[inline]
    pub fn is_added(&self) -> bool {
        self.ticks.is_added(self.last_change_tick, self.change_tick)
    }

    #[inline]
    pub fn is_changed(&self) -> bool {
        self.ticks
            .is_changed(self.last_change_tick, self.change_tick)
    }
}

impl<'w, T> Deref for Res<'w, T> {
    type Target = T;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.value
    }
}

impl<'w, T> AsRef<T> for Res<'w, T> {
    #[inline]
    fn as_ref(&self) -> &T {
        self.value
    }
}

pub struct ResState<T> {
    component_id: ComponentId,
    marker: PhantomData<T>,
}

unsafe impl<T: Resource> SystemParamState for ResState<T> {
    fn init(world: &mut World, meta: &mut SystemMeta) -> Self {
        let component_id = world.components.init_resource::<T>();

        meta.access.add_read(component_id);

        Self {
            component_id,
            marker: PhantomData,
        }
    }
}

impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState<T> {
    type Item = Res<'w, T>;

    unsafe fn get_param(
        &'s mut self,
        meta: &SystemMeta,
        world: &'w World,
        change_tick: u32,
    ) -> Self::Item {
        let (ptr, ticks) = world
            .resources
            .get_with_ticks(self.component_id)
            .unwrap_or_else(|| {
                panic!(
                    "Resource requested by system {} does not exist: {}.",
                    meta.name(),
                    std::any::type_name::<T>()
                )
            });

        Res {
            value: unsafe { &*ptr.cast::<T>() },
            ticks: unsafe { &*ticks },
            last_change_tick: meta.last_change_tick,
            change_tick,
        }
    }
}

impl<'w, T: Resource> SystemParam for Res<'w, T> {
    type Fetch = ResState<T>;
}

pub struct ResMut<'w, T> {
    value: &'w mut T,
    ticks: &'w mut ChangeTicks,
    last_change_tick: u32,
    change_tick: u32,
}

impl<'w, T> ResMut<'w, T> {
    #[inline]
    pub fn is_added(&self) -> bool {
        self.ticks.is_added(self.last_change_tick, self.change_tick)
    }

    #[inline]
    pub fn is_changed(&self) -> bool {
        self.ticks
            .is_changed(self.last_change_tick, self.change_tick)
    }
}

impl<'w, T> Deref for ResMut<'w, T> {
    type Target = T;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.value
    }
}

impl<'w, T> DerefMut for ResMut<'w, T> {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.ticks.set_changed(self.change_tick);
        self.value
    }
}

pub struct ResMutState<T> {
    component_id: ComponentId,
    marker: PhantomData<T>,
}

unsafe impl<T: Resource> SystemParamState for ResMutState<T> {
    fn init(world: &mut World, meta: &mut SystemMeta) -> Self {
        let component_id = world.components.init_resource::<T>();

        meta.access.add_write(component_id);

        Self {
            component_id,
            marker: PhantomData,
        }
    }
}

impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState<T> {
    type Item = ResMut<'w, T>;

    unsafe fn get_param(
        &'s mut self,
        meta: &SystemMeta,
        world: &'w World,
        change_tick: u32,
    ) -> Self::Item {
        let (ptr, ticks) = world
            .resources
            .get_with_ticks(self.component_id)
            .unwrap_or_else(|| {
                panic!(
                    "Resource requested by system {} does not exist: {}.",
                    meta.name(),
                    std::any::type_name::<T>()
                )
            });

        ResMut {
            value: unsafe { &mut *ptr.cast::<T>() },
            ticks: unsafe { &mut *ticks },
            last_change_tick: meta.last_change_tick,
            change_tick,
        }
    }
}

impl<'w, T: Resource> SystemParam for ResMut<'w, T> {
    type Fetch = ResMutState<T>;
}

macro_rules! impl_system_param {
    (@ $($param:ident),*) => {
        impl<$($param: SystemParam),*> SystemParam for ($($param,)*) {
            type Fetch = ($($param::Fetch,)*);
        }

        #[allow(non_snake_case, unused)]
        unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) {
            #[inline]
            fn init(world: &mut World, meta: &mut SystemMeta) -> Self {
                ($($param::init(world, meta),)*)
            }

            #[inline]
            fn apply(&mut self, world: &mut World) {
                let ($($param,)*) = self;
                $($param.apply(world);)*
            }
        }

        #[allow(non_snake_case, unused)]
        impl<'w, 's, $($param: SystemParamFetch<'w, 's>),*> SystemParamFetch<'w, 's> for ($($param,)*) {
            type Item = ($($param::Item,)*);

            unsafe fn get_param(
                &'s mut self,
                meta: &SystemMeta,
                world: &'w World,
                change_tick: u32,
            ) -> Self::Item {
                let ($($param,)*) = self;
                unsafe { ($($param.get_param(meta, world, change_tick),)*) }
            }
        }
    };
    ($start:ident $(,$ident:ident)*) => {
        impl_system_param!(@ $start $(,$ident)*);
        impl_system_param!($($ident),*);
    };
    () => {
        impl_system_param!(@);
    };
}

impl_system_param!(A, B, C, D, E, F, G, H, I, J, K, L);