From a68b0a7fb47661b039d3d491825411016d98ac57 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 9 Dec 2023 14:39:01 -0500 Subject: [PATCH] Simple dynamic views --- lyra-ecs/src/archetype.rs | 5 ++ lyra-ecs/src/component_info.rs | 8 +- lyra-ecs/src/query/dynamic.rs | 65 -------------- lyra-ecs/src/query/dynamic/mod.rs | 88 ++++++++++++++++++ lyra-ecs/src/query/dynamic/view.rs | 139 +++++++++++++++++++++++++++++ lyra-ecs/src/query/mod.rs | 2 - 6 files changed, 236 insertions(+), 71 deletions(-) delete mode 100644 lyra-ecs/src/query/dynamic.rs create mode 100644 lyra-ecs/src/query/dynamic/mod.rs create mode 100644 lyra-ecs/src/query/dynamic/view.rs diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 113c3a3..48e3ded 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -280,6 +280,11 @@ impl Archetype { self.columns.iter().all(|c| types.contains(&c.info.type_id)) } + /// Returns a boolean indicating whether this archetype has a column for `comp_type` + pub(crate) fn has_column(&self, comp_type: DynTypeId) -> bool { + self.columns.iter().any(|c| comp_type == c.info.type_id) + } + /// Returns a boolean indicating whether this archetype is empty or not. pub fn is_empty(&self) -> bool { self.entities.is_empty() diff --git a/lyra-ecs/src/component_info.rs b/lyra-ecs/src/component_info.rs index 515c8b8..476d530 100644 --- a/lyra-ecs/src/component_info.rs +++ b/lyra-ecs/src/component_info.rs @@ -61,10 +61,10 @@ impl DynTypeId { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComponentInfo { pub type_id: DynTypeId, - pub name: String, + //pub name: String, pub layout: MemoryLayout, } @@ -72,7 +72,7 @@ impl ComponentInfo { pub fn new() -> Self { Self { type_id: DynTypeId::from(TypeId::of::()), - name: type_name::().to_string(), + //name: type_name::().to_string(), layout: MemoryLayout::from(Layout::new::()), } } @@ -84,7 +84,7 @@ impl ComponentInfo { { Self { type_id: type_id.into(), - name: name.to_string(), + //name: name.to_string(), layout, } } diff --git a/lyra-ecs/src/query/dynamic.rs b/lyra-ecs/src/query/dynamic.rs deleted file mode 100644 index b27313a..0000000 --- a/lyra-ecs/src/query/dynamic.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::ptr::NonNull; - -use crate::{Fetch, world::World, Query}; - -#[derive(Clone, Copy, Hash)] -enum TypeId { - Rust(std::any::TypeId), - Unknown(u128), -} - -#[derive(Clone, Copy, Hash)] -pub struct DynamicTypeInfo { - id: TypeId, - size: usize, - alignment: usize, -} - -/// Data that is unknown to rust -pub struct DynamicType { - info: DynamicTypeInfo, - ptr: NonNull, -} - -pub struct FetchDynamicType { - info: Option, -} - -impl<'a> Fetch<'a> for FetchDynamicType { - type Item = DynamicType; - - fn dangling() -> Self { - Self { - info: None, - } - } - - unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { - todo!() - } -} - -#[derive(Clone, Copy)] -pub struct QueryDynamicType { - info: DynamicTypeInfo, -} - -impl Query for QueryDynamicType { - type Item<'a> = DynamicType; - - type Fetch<'a> = FetchDynamicType; - - const ALWAYS_FETCHES: bool = false; - - fn new() -> Self { - todo!() - } - - fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool { - todo!() - } - - unsafe fn fetch<'a>(&self, world: &'a World, _arch_id: crate::archetype::ArchetypeId, _archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { - todo!() - } -} \ No newline at end of file diff --git a/lyra-ecs/src/query/dynamic/mod.rs b/lyra-ecs/src/query/dynamic/mod.rs new file mode 100644 index 0000000..151d533 --- /dev/null +++ b/lyra-ecs/src/query/dynamic/mod.rs @@ -0,0 +1,88 @@ +use std::{ptr::NonNull, cell::Ref}; + +use crate::{Fetch, world::World, Query, ComponentColumn, ComponentInfo}; + +pub mod view; +pub use view::*; + +#[derive(Clone, Copy, Hash)] +enum TypeId { + Rust(std::any::TypeId), + Unknown(u128), +} + +/// Data that rust does not know the type of +pub struct DynamicType { + pub info: ComponentInfo, + pub ptr: NonNull, +} + +pub struct FetchDynamicType<'a> { + col: &'a ComponentColumn, + info: ComponentInfo, +} + +impl<'a> Fetch<'a> for FetchDynamicType<'a> { + type Item = DynamicType; + + fn dangling() -> Self { + unreachable!() + } + + unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { + let ptr = self.col.borrow_ptr(); + let ptr = NonNull::new_unchecked(ptr.as_ptr() + .add(entity.0 as usize * self.info.layout.size)); + + DynamicType { + info: self.info, + ptr, + } + } +} + +pub trait DynamicQuery: Query { + +} + +/// A query for receiving a dynamic type. +/// +/// There are no Ref or RefMut variants since this fetches the pointer to the component. +#[derive(Clone, Copy)] +pub struct QueryDynamicType { + info: ComponentInfo, +} + +impl QueryDynamicType { + pub fn from_info(info: ComponentInfo) -> Self { + Self { + info, + } + } +} + +impl Query for QueryDynamicType { + type Item<'a> = DynamicType; + + type Fetch<'a> = FetchDynamicType<'a>; + + const ALWAYS_FETCHES: bool = false; + + fn new() -> Self { + panic!("QueryDynamicType does not implement QueryDefault, this should not have been called") + } + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + archetype.has_column(self.info.type_id) + } + + unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + let col = archetype.columns.iter().find(|c| c.info.type_id == self.info.type_id) + .expect("You ignored 'can_visit_archetype'!"); + + FetchDynamicType { + col, + info: self.info, + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/dynamic/view.rs b/lyra-ecs/src/query/dynamic/view.rs new file mode 100644 index 0000000..8209c21 --- /dev/null +++ b/lyra-ecs/src/query/dynamic/view.rs @@ -0,0 +1,139 @@ +use std::ops::Range; + +use crate::{world::World, Archetype, Query, ArchetypeEntityId, Fetch, ArchetypeId}; + +use super::{DynamicQuery, QueryDynamicType, FetchDynamicType}; + +pub struct DynamicView<'a> { + world: &'a World, + queries: Vec +} + +impl<'a> DynamicView<'a> { + pub fn new(world: &'a World) -> Self { + Self { + world, + queries: Vec::new(), + } + } + + pub fn push(&mut self, dyn_query: QueryDynamicType) { + self.queries.push(dyn_query); + } +} + +impl<'a> IntoIterator for DynamicView<'a> { + type Item = Vec<::Item<'a>>; + + type IntoIter = DynamicViewIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + let archetypes = self.world.archetypes.values().collect(); + + DynamicViewIter { + world: self.world, + queries: self.queries, + fetchers: Vec::new(), + archetypes: archetypes, + next_archetype: 0, + component_indices: 0..0, + } + } +} + +pub struct DynamicViewIter<'a> { + world: &'a World, + queries: Vec, + fetchers: Vec>, + archetypes: Vec<&'a Archetype>, + next_archetype: usize, + component_indices: Range, +} + +impl<'a> Iterator for DynamicViewIter<'a> { + type Item = Vec<::Item<'a>>; + + fn next(&mut self) -> Option { + loop { + if let Some(entity_index) = self.component_indices.next() { + let mut fetch_res = vec![]; + + //let fetcher = self.fetcher.as_mut().unwrap(); + for fetcher in self.fetchers.iter_mut() { + let entity_index = ArchetypeEntityId(entity_index); + if !fetcher.can_visit_item(entity_index) { + break; + } else { + let i = unsafe { fetcher.get_item(entity_index) }; + fetch_res.push(i); + } + } + + if fetch_res.len() != self.fetchers.len() { + continue; + } + + return Some(fetch_res); + } else { + if self.next_archetype >= self.archetypes.len() { + return None; // ran out of archetypes to go through + } + + let arch_id = self.next_archetype; + self.next_archetype += 1; + let arch = unsafe { self.archetypes.get_unchecked(arch_id) }; + + if arch.entities.len() == 0 { + continue; + } + + if self.queries.iter().any(|q| !q.can_visit_archetype(arch)) { + continue; + } + + self.fetchers = self.queries.iter() + .map(|q| unsafe { q.fetch(self.world, ArchetypeId(arch_id as u64), arch) } ) + .collect(); + self.component_indices = 0..arch.entities.len() as u64; + } + } + } +} + +#[cfg(test)] +mod tests { + use std::{alloc::Layout, ptr::NonNull}; + + use crate::{world::World, MemoryLayout, ComponentInfo, DynTypeId, DynamicBundle, dynamic::{DynamicQuery, QueryDynamicType}}; + + use super::DynamicView; + + #[test] + fn single_dynamic_view() { + let comp_layout = MemoryLayout::from(Layout::new::()); + let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout); + + let mut dynamic_bundle = DynamicBundle::default(); + let comp = 50u32; + let ptr = NonNull::from(&comp).cast::(); + dynamic_bundle.push_unknown(ptr, comp_info.clone()); + + let mut world = World::new(); + world.spawn(dynamic_bundle); + + let query = QueryDynamicType::from_info(comp_info); + let mut view = DynamicView::new(&world); + view.push(query); + + for view_row in view.into_iter() { + assert_eq!(view_row.len(), 1); + + let mut row_iter = view_row.iter(); + + let dynamic_type = row_iter.next().unwrap(); + + let component_data = unsafe { dynamic_type.ptr.cast::().as_ref() }; + assert_eq!(*component_data, 50); + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index b338381..074a873 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -20,8 +20,6 @@ pub mod resource; pub use resource::*; pub mod dynamic; -#[allow(unused_imports)] -pub use dynamic::*; /// A [`Fetch`]er implementation gets data out of an archetype. pub trait Fetch<'a> {