nya-ecs 0.2.0

nya entity component system
Documentation
//! Query functions, "Systems"

use std::marker::PhantomData;

use crate::{
    World,
    query::{Query, Queryable, ToFilter},
};

use indexmap::IndexMap;
use nya_core::sparse_set::SparseSet;

/// System trait
pub trait System: 'static {
    /// Run the system on `world`
    fn run(&self, world: &mut World);
}

/// "Dyanmic" System trait
pub struct QuerySystem<F: ToFilter, C: Fn(Query, &mut World) = fn(Query, &mut World)> {
    callback: C,
    _marker: PhantomData<F>,
}

impl<F: ToFilter, C: Fn(Query, &mut World)> QuerySystem<F, C> {
    /// Create a new [QuerySystem]
    pub const fn new(callback: C) -> Self {
        Self {
            callback,
            _marker: PhantomData,
        }
    }
}

impl<F: ToFilter, C: Fn(Query, &mut World)> System for QuerySystem<F, C>
where
    Self: 'static,
{
    fn run(&self, world: &mut World) {
        let query = world.query::<F>();
        (self.callback)(query, world);
    }
}

/// Manages systems for a [World] in multiple runlevels
pub struct SystemManager {
    /// The World
    pub world: World,
    runlevels: IndexMap<usize, SparseSet<Box<dyn System>>>,
}

impl SystemManager {
    /// Create new [SystemManager]
    pub fn new(world: World) -> Self {
        SystemManager {
            world,
            runlevels: IndexMap::with_capacity(2),
        }
    }

    /// Return whether runlevel `level` exists
    pub fn has_runlevel(&self, level: usize) -> bool {
        self.runlevels.contains_key(&level)
    }

    /// Add a [System] to runlevel `level`, return the system id
    pub fn add_system<S: System>(&mut self, level: usize, system: S) -> usize {
        self.runlevels
            .entry(level)
            .or_default()
            .insert(Box::new(system))
    }

    /// Remove a [System] from runlevel `level`
    pub fn remove_system<S: System>(&mut self, level: usize, system: usize) {
        self.runlevels.entry(level).or_default().remove(system);
    }

    /// Run systems of in runlevel `level`
    pub fn run_systems(&mut self, level: usize) {
        if let Some(systems) = self.runlevels.get(&level) {
            for s in systems {
                s.value.run(&mut self.world);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        Exclude, World, component,
        system::{QuerySystem, SystemManager},
    };

    #[allow(dead_code)]
    struct Tag(i32);
    component!(Tag);

    struct Hello;
    component!(Hello);

    #[test]
    fn basic_query() {
        let mut manager = SystemManager::new(World::new());

        for i in 0..10 {
            let e = manager.world.spawn();
            manager.world.add(e, Tag(32));

            if i % 2 == 0 {
                manager.world.add(e, Hello);
            }
        }

        manager.add_system(
            0,
            QuerySystem::<(Tag, Exclude<(Hello,)>)>::new(|query, world| {
                let _ = world;
                println!("{:?}", query.entities());
            }),
        );

        manager.run_systems(0);
        manager.run_systems(1); // runlevel does not exist
    }
}