sky_ecs 0.1.0

Chunk-based ECS core for Sky
Documentation
mod filter;
mod parallel;
mod param;
mod prepared;

use super::{Chunk, EntityId, World};
use crate::ecs::ComponentType;
use core::ptr;
use smallvec::SmallVec;

pub use filter::{QueryFilter, With, Without};
pub use param::{QuerySpec, ReadOnlyQuerySpec};
pub use prepared::PreparedQuery;

const INLINE_QUERY_COMPONENTS: usize = 8;
const OPTIONAL_SENTINEL: u8 = u8::MAX;

#[derive(Clone, Copy)]
pub struct QueryComponent {
    pub(crate) ty: ComponentType,
    pub(crate) mutable: bool,
    pub(crate) optional: bool,
}

impl QueryComponent {
    pub(crate) fn new(ty: ComponentType, mutable: bool) -> Self {
        Self {
            ty,
            mutable,
            optional: false,
        }
    }

    pub(crate) fn optional(ty: ComponentType, mutable: bool) -> Self {
        Self {
            ty,
            mutable,
            optional: true,
        }
    }
}

pub struct QueryDescriptor {
    pub(crate) components: SmallVec<[QueryComponent; INLINE_QUERY_COMPONENTS]>,
}

impl QueryDescriptor {
    pub(crate) fn new(components: SmallVec<[QueryComponent; INLINE_QUERY_COMPONENTS]>) -> Self {
        for (index, component) in components.iter().enumerate() {
            for other in &components[(index + 1)..] {
                if component.ty.id() == other.ty.id() {
                    let access_mode = if component.mutable || other.mutable {
                        "mutable"
                    } else {
                        "shared"
                    };
                    panic!(
                        "duplicate component type `{}` is not supported in {} queries",
                        component.ty.name, access_mode
                    );
                }
            }
        }

        Self { components }
    }

    pub(crate) fn len(&self) -> usize {
        self.components.len()
    }
}

#[derive(Clone)]
pub(crate) struct CachedArchetype {
    pub data_index: usize,
    pub component_indices: SmallVec<[u8; INLINE_QUERY_COMPONENTS]>,
}

#[derive(Default)]
pub(crate) struct PreparedCache {
    cached_epoch: Option<usize>,
    pub archetypes: Vec<CachedArchetype>,
}

#[doc(hidden)]
pub trait QueryWorld<Q: QuerySpec>: sealed::QueryWorldSealed {
    fn as_world(&self) -> &World;
}

mod sealed {
    use super::World;

    pub trait QueryWorldSealed {}

    impl QueryWorldSealed for &World {}
    impl QueryWorldSealed for &mut World {}
}

impl<Q: QuerySpec> QueryWorld<Q> for &mut World {
    #[inline(always)]
    fn as_world(&self) -> &World {
        self
    }
}

impl<Q: ReadOnlyQuerySpec> QueryWorld<Q> for &World {
    #[inline(always)]
    fn as_world(&self) -> &World {
        self
    }
}

impl PreparedCache {
    #[inline(always)]
    pub fn prepare<Flt: QueryFilter>(&mut self, world: &World, descriptor: &QueryDescriptor) {
        let current_epoch = world.archetype_epoch();
        if self.cached_epoch == Some(current_epoch) {
            return;
        }

        self.archetypes.clear();

        for (data_index, data) in world.data.iter().enumerate() {
            let archetype = data.archetype;

            if !Flt::matches_archetype(&archetype) {
                continue;
            }

            let mut component_indices =
                SmallVec::<[u8; INLINE_QUERY_COMPONENTS]>::with_capacity(descriptor.len());

            let mut matches = true;
            for component in &descriptor.components {
                if let Some(index) = archetype.query_component_index(&component.ty) {
                    debug_assert!(index < OPTIONAL_SENTINEL as usize);
                    component_indices.push(index as u8);
                } else if component.optional {
                    component_indices.push(OPTIONAL_SENTINEL);
                } else {
                    matches = false;
                    break;
                }
            }

            if matches {
                self.archetypes.push(CachedArchetype {
                    data_index,
                    component_indices,
                });
            }
        }

        self.cached_epoch = Some(current_epoch);
    }

    #[inline(always)]
    pub fn visit_chunks<'w, F>(&self, world: &'w World, mut f: F)
    where
        F: FnMut(&CachedArchetype, &'w Chunk),
    {
        for cached in &self.archetypes {
            let data = &world.data[cached.data_index];

            for chunk in &data.chunks {
                debug_assert!(chunk.entity_count != 0);
                f(cached, chunk);
            }
        }
    }

    pub fn cached_archetype_count(&self) -> usize {
        self.archetypes.len()
    }
}

#[inline(always)]
pub(crate) fn resolve_column_ptr(chunk: &Chunk, index: u8) -> *mut u8 {
    if index == OPTIONAL_SENTINEL {
        ptr::null_mut()
    } else {
        chunk.column_ptr(index as usize)
    }
}