diff --git a/lyra-ecs/.vscode/launch.json b/lyra-ecs/.vscode/launch.json index d971c77..97f385b 100644 --- a/lyra-ecs/.vscode/launch.json +++ b/lyra-ecs/.vscode/launch.json @@ -31,7 +31,9 @@ "test", "--no-run", "--bin=lyra-ecs", - "--package=lyra-ecs" + "--package=lyra-ecs", + "--", + "--nocapture" ], "filter": { "name": "lyra-ecs", diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 99b6342..b9d592b 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,55 +1,96 @@ -use std::any::{Any, TypeId}; +use std::{any::{Any, TypeId, type_name}, ptr::{NonNull, self}, alloc::{self, Layout, alloc}, mem::size_of}; -use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component::Component}; +use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component::Component, component_info::ComponentInfo}; -pub trait ComponentColumn: Any { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; - fn new_empty_column(&self) -> Box; - fn is_same_type(&self, column: &dyn ComponentColumn) -> bool; - fn len(&self) -> usize; - fn append(&mut self, column: &mut dyn ComponentColumn); - - fn component_type_id(&self) -> TypeId; - // used for debugging - fn component_type_name(&self) -> String; +pub struct ComponentColumn { + pub data: NonNull, + pub capacity: usize, + pub info: ComponentInfo, + pub entry_size: usize, } -impl ComponentColumn for Vec { - fn as_any(&self) -> &dyn Any { - self +impl ComponentColumn { + /// Creates an invalid component column. Do not attempt to use it. + pub unsafe fn dangling() -> Self { + ComponentColumn { + data: NonNull::dangling(), + capacity: 0, + info: ComponentInfo::new::<()>(), + entry_size: 0, + } } - fn as_any_mut(&mut self) -> &mut dyn Any { - self + pub unsafe fn alloc(component_layout: Layout, capacity: usize) -> NonNull { + let new_layout = Layout::from_size_align( + component_layout.size().checked_mul(capacity).unwrap(), + component_layout.align() + ).unwrap(); + + if let Some(data) = NonNull::new(alloc(new_layout)) { + data + } else { + alloc::handle_alloc_error(new_layout) + } } - fn new_empty_column(&self) -> Box { - Box::new(Vec::::new()) + pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self { + let data = ComponentColumn::alloc(info.layout, capacity); + + let size = info.layout.size(); + Self { + data, + capacity, + info, + entry_size: size, + } } - fn is_same_type(&self, column: &dyn ComponentColumn) -> bool { - column.as_any().downcast_ref::().is_some() + /// Creates an empty column of the same type + pub unsafe fn create_empty(&self, capacity: usize) -> Self { + let data = ComponentColumn::alloc(self.info.layout, capacity); + + let size = self.info.layout.size(); + Self { + data, + capacity, + info: self.info.clone(), + entry_size: size, + } } - fn len(&self) -> usize { - Vec::len(self) + /// Set a component from pointer at an entity index. + /// + /// # Safety + /// + /// This column must have space to fit the component, if it does not have room it will panic. + pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull) { + assert!(entity_index < self.capacity); + let dest = NonNull::new_unchecked(self.data.as_ptr().add(entity_index * self.entry_size)); + ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), self.entry_size); } - fn append(&mut self, column: &mut dyn ComponentColumn) { - let column: &mut Self = column.as_any_mut().downcast_mut() - .expect("Attempt at appending an different column type!"); - - self.append(column); + /// Get a component at an entities index. + /// + /// # Safety + /// + /// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB. + pub unsafe fn get(&self, entity_index: usize) -> &T { + let p = self.data.as_ptr() + .cast::() + .add(entity_index * self.entry_size); + &*p } - fn component_type_id(&self) -> TypeId { - self.first().unwrap().type_id() - } - - fn component_type_name(&self) -> String { - //self.first().unwrap().type_id() - std::any::type_name::().to_string() + /// Get a component at an entities index. + /// + /// # Safety + /// + /// This column must have the entity. + pub unsafe fn get_mut(&mut self, entity_index: usize) -> &mut T { + let p = self.data.as_ptr() + .cast::() + .add(entity_index * self.entry_size); + &mut *p } } @@ -68,24 +109,25 @@ impl ArchetypeId { pub struct Archetype { pub(crate) id: ArchetypeId, - entities: Vec, - pub(crate) columns: Vec>, + pub(crate) entities: Vec, + pub(crate) columns: Vec, } +/// The default capacity of the columns +const DEFAULT_CAPACITY: usize = 32; + impl Archetype { /// Create a new archetype from another archetype and add a column pub fn new_archetype_add(new_id: ArchetypeId, archetype: &Archetype) -> Archetype { let mut columns: Vec<_> = archetype .columns .iter() - .map(|c| c.new_empty_column()) + .map(|c| unsafe { c.create_empty(DEFAULT_CAPACITY) }) .collect(); - assert!(columns - .iter() - .find(|column| column.as_any().is::>()) - .is_none()); - columns.push(Box::new(Vec::::new())); + // Make sure a column for the new component does not exist + assert_ne!(true, columns.iter().any(|c| c.info.type_id == TypeId::of::()) ); + columns.push(unsafe { ComponentColumn::new(ComponentInfo::new::(), DEFAULT_CAPACITY) }); Archetype { id: new_id, @@ -99,12 +141,12 @@ impl Archetype { let mut columns: Vec<_> = archetype .columns .iter() - .map(|c| c.new_empty_column()) + .map(|c| unsafe { c.create_empty(DEFAULT_CAPACITY) }) .collect(); let idx = columns .iter() - .position(|column| column.as_any().is::>()) + .position(|column| column.info.type_id == TypeId::of::()) .unwrap(); columns.remove(idx); @@ -115,7 +157,11 @@ impl Archetype { } } - pub fn from_columns(new_id: ArchetypeId, columns: Vec>) -> Archetype { + pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec) -> Archetype { + let columns = bundle_info.into_iter().map(|i| { + unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) } + }).collect(); + Archetype { id: new_id, entities: Vec::new(), @@ -124,64 +170,42 @@ impl Archetype { } pub fn get_component_mut(&mut self, entity: ArchetypeEntityId) -> Option<&mut T> { - for col in self.columns.iter_mut() { - if col.as_any().is::>() { - let components: &mut Vec = col.as_any_mut().downcast_mut().unwrap(); - - return components.get_mut(entity.0 as usize); - } - } - - None + todo!() } - pub fn get_component(&self, entity: ArchetypeEntityId) -> Option<&T> { - for col in self.columns.iter() { - if col.as_ref().as_any().is::>() { - let components: &Vec = col.as_any().downcast_ref().unwrap(); - - return components.get(entity.0 as usize); - } - } - - None + pub fn get_entity_component(&self, entity: ArchetypeEntityId) -> Option<&T> { + let type_id = TypeId::of::(); + + self.columns.iter().find(|c| c.info.type_id == type_id) + .map(|c| unsafe { c.get(entity.0 as usize) }) } pub fn get_component_column(&self) -> Option<&Vec> { - let col = self.columns.iter().find(|c| c.as_any().is::>())?; - col.as_any().downcast_ref() + todo!() } - pub(crate) fn add_entity(&mut self, components: Vec>) -> ArchetypeEntityId { - let mut created_entity: Option = None; + /// Add an entity and its component bundle to the Archetype + /// + /// # Safety: + /// + /// Archetype must contain all of the components + pub(crate) fn add_entity(&mut self, entity: Entity, bundle: B) -> ArchetypeEntityId + where + B: Bundle + { + let entity_index = self.entities.len(); + self.entities.push(entity); - for mut component in components.into_iter() { - for col in self.columns.iter_mut() { - if col.is_same_type(component.as_ref()) { - match created_entity { - Some(e) => { - assert!(e.0 == col.len() as u64); - }, - None => { - created_entity = Some(ArchetypeEntityId(col.len() as u64)); - } - } - - col.append(component.as_mut()); - } - } - } - - created_entity.expect("Failure to create entity!") + bundle.takefn(|data, type_id, size| { + let col = self.columns.iter_mut().find(|c| c.info.type_id == type_id).unwrap(); + unsafe { col.set_at(entity_index, data); } + }); + + ArchetypeEntityId(entity_index as u64) } /// returns a boolean indicating whether this archetype can store the TypeIds given pub(crate) fn is_archetype_for(&self, types: Vec) -> bool { - let types_iter = types.into_iter(); - - self.columns - .iter() - .map(|c| c.component_type_id()) - .eq(types_iter) + self.columns.iter().all(|c| types.contains(&c.info.type_id)) } } \ No newline at end of file diff --git a/lyra-ecs/src/bundle.rs b/lyra-ecs/src/bundle.rs index 287875e..9fbacdb 100644 --- a/lyra-ecs/src/bundle.rs +++ b/lyra-ecs/src/bundle.rs @@ -1,6 +1,6 @@ -use std::any::{TypeId, Any}; +use std::{any::{TypeId, Any}, cell::Ref, ptr::NonNull, mem::size_of}; -use crate::{archetype::ComponentColumn, component::Component}; +use crate::{archetype::{ComponentColumn, Archetype}, component::Component, component_info::ComponentInfo}; pub trait Bundle { // Get a list of type ids that this bundle is storing @@ -8,10 +8,35 @@ pub trait Bundle { /// Take components into a list. /// The return value could be seen as a list of a list of components, but that inner list /// only contains a single value to make it easier to add it to an archetype. - fn take_components(self) -> Vec>; + //fn take_components(self) -> Vec>; + + //fn from_component_columns(columns: Vec<&Box>) -> Self; + //fn from_archetype(archetype: &Archetype) -> Self; + + fn info(&self) -> Vec; + + /// Take the bundle by calling the closure with pointers to each component, its type and size. + /// The closure is expected to take ownership of the pointer. + fn takefn(self, cb: impl FnMut(NonNull, TypeId, usize)); } -macro_rules! impl_bundle_tuple { +impl Bundle for (C1,) { + fn types(&self) -> Vec { + vec![self.0.type_id()] + } + + fn info(&self) -> Vec { + vec![ComponentInfo::new::()] + } + + fn takefn(self, mut cb: impl FnMut(NonNull, TypeId, usize)) { + let (c1, ) = self; + + cb(NonNull::from(&c1).cast(), TypeId::of::(), size_of::()); + } +} + +/* macro_rules! impl_bundle_tuple { ( $(($name: ident, $index: tt))+ ) => ( impl<$($name: Component),+> Bundle for ($($name,)+) { fn types(&self) -> Vec { @@ -21,13 +46,23 @@ macro_rules! impl_bundle_tuple { fn take_components(self) -> Vec> { vec![$(Box::new(vec![self.$index])),+] } + + fn from_component_columns(columns: Vec<&Box>) -> Self { + let mut chains = Vec::new(); + + for col in columns.iter() { + col. + } + + todo!() + } } ); -} +} */ // hopefully 16 components in a bundle is enough -impl_bundle_tuple! { (C1, 0) } -impl_bundle_tuple! { (C1, 0) (C2, 1) } +//impl_bundle_tuple! { (C1, 0) } +/* impl_bundle_tuple! { (C1, 0) (C2, 1) } impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) } impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) } impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) } @@ -41,3 +76,4 @@ impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8 impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) } impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) (C14, 13) } impl_bundle_tuple! { (C1, 0) (C2, 1) (C3, 2) (C4, 3) (C5, 4) (C6, 5) (C7, 6) (C8, 7) (C9, 8) (C10, 9) (C11, 10) (C12, 11) (C13, 12) (C14, 13) (C15, 14) } + */ \ No newline at end of file diff --git a/lyra-ecs/src/component_info.rs b/lyra-ecs/src/component_info.rs new file mode 100644 index 0000000..ed6c8e1 --- /dev/null +++ b/lyra-ecs/src/component_info.rs @@ -0,0 +1,18 @@ +use std::{any::{TypeId, type_name}, alloc::Layout}; + +#[derive(Clone, Debug)] +pub struct ComponentInfo { + pub type_id: TypeId, + pub name: String, + pub layout: Layout, +} + +impl ComponentInfo { + pub fn new() -> Self { + Self { + type_id: TypeId::of::(), + name: type_name::().to_string(), + layout: Layout::new::(), + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/main.rs b/lyra-ecs/src/main.rs index 81973c2..9762d55 100644 --- a/lyra-ecs/src/main.rs +++ b/lyra-ecs/src/main.rs @@ -1,9 +1,12 @@ +//#![feature(cell_leak)] use crate::world::World; mod archetype; mod world; mod bundle; mod component; +mod query; +mod component_info; #[derive(Debug)] pub struct Position2d(i32, i32); @@ -25,7 +28,14 @@ fn main() { println!("\nstart of querying!\n"); - for pos in world.query::() { + /* for pos in world.query::() { + println!("Queried Position2d: {:?}", pos); + } */ + + let mut q = world.query_better(); + q.with_component::(); + + for pos in q.run::() { println!("Queried Position2d: {:?}", pos); } } diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs new file mode 100644 index 0000000..f5c33e8 --- /dev/null +++ b/lyra-ecs/src/query/borrow.rs @@ -0,0 +1,204 @@ +use std::{marker::PhantomData, any::TypeId, ptr::NonNull}; + +use super::{Fetch, Query}; + +pub struct FetchBorrow<'a, T> { + ptr: NonNull, + size: usize, + _phantom: PhantomData<&'a T> +} + +impl<'a, T> Fetch<'a> for FetchBorrow<'a, T> +where + T: 'a, +{ + type Item = &'a T; + + fn create_empty() -> Self { + FetchBorrow { + ptr: NonNull::dangling(), + size: 0, + _phantom: PhantomData::default() + } + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let ptr = NonNull::new_unchecked(self.ptr.as_ptr() + .add(entity.0 as usize * self.size)) + .cast(); + &*ptr.as_ptr() + } +} + +pub struct QueryBorrow { + type_id: TypeId, + _phantom: PhantomData +} + +impl Query for QueryBorrow +where + T: 'static +{ + type Item<'a> = &'a T; + + type Fetch<'a> = FetchBorrow<'a, T>; + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + } + + unsafe fn fetch<'a>(&self, _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.type_id) + .expect("You ignored 'can_visit_archetype'!"); + let col_data = col.data; + + FetchBorrow { + ptr: NonNull::new_unchecked(col_data.as_ptr()), + size: col.info.layout.size(), + _phantom: PhantomData, + } + } +} + +pub struct FetchBorrowMut<'a, T> { + ptr: NonNull, + size: usize, + _phantom: PhantomData<&'a T> +} + +impl<'a, T> Fetch<'a> for FetchBorrowMut<'a, T> +where + T: 'a, +{ + type Item = &'a mut T; + + fn create_empty() -> Self { + FetchBorrowMut { + ptr: NonNull::dangling(), + size: 0, + _phantom: PhantomData::default() + } + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let ptr = NonNull::new_unchecked(self.ptr.as_ptr() + .add(entity.0 as usize * self.size)) + .cast(); + &mut *ptr.as_ptr() + } +} + +pub struct QueryBorrowMut { + type_id: TypeId, + _phantom: PhantomData +} + +impl Query for QueryBorrowMut +where + T: 'static +{ + type Item<'a> = &'a mut T; + + type Fetch<'a> = FetchBorrowMut<'a, T>; + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + } + + unsafe fn fetch<'a>(&self, _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.type_id) + .expect("You ignored 'can_visit_archetype'!"); + let col_data = col.data; + + FetchBorrowMut { + ptr: NonNull::new_unchecked(col_data.as_ptr()), + size: col.info.layout.size(), + _phantom: PhantomData, + } + } +} + +#[cfg(test)] +mod tests { + use std::any::TypeId; + + use crate::{world::World, archetype::Archetype, query::View}; + + use super::{QueryBorrow, QueryBorrowMut}; + + #[derive(Clone, Copy, Debug, Default)] + struct Vec2 { + x: f32, + y: f32, + } + + impl Vec2 { + pub fn new(x: f32, y: f32) -> Self { + Self { + x, + y, + } + } + } + + /// Creates a world with two entities, one at Vec(10, 50) and + /// the other at Vec2(25, 30). + fn prepare_world() -> World { + let mut world = World::new(); + + world.spawn((Vec2::new(10.0, 50.0),)); + world.spawn((Vec2::new(25.0, 30.0),)); + world + } + + #[test] + fn borrow_query() { + let world = prepare_world(); + + let borrow = QueryBorrow:: { + type_id: TypeId::of::(), + _phantom: std::marker::PhantomData, + }; + let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); + let v = View::new(borrow, archetypes); + + for e in v.into_iter() { + println!("Found entity at {:?}", e); + } + } + + #[test] + fn borrow_mut_query() { + let world = prepare_world(); + + let borrow = QueryBorrowMut:: { + type_id: TypeId::of::(), + _phantom: std::marker::PhantomData, + }; + let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); + let v = View::new(borrow, archetypes); + + let mut orig = vec![]; + + for v in v.into_iter() { + orig.push(v.clone()); + v.x += 10.0; + v.y += 10.0; + } + + // Now make sure the changes were actually made + + let borrow = QueryBorrow:: { + type_id: TypeId::of::(), + _phantom: std::marker::PhantomData, + }; + let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); + let v = View::new(borrow, archetypes); + + for (new, orig) in v.into_iter().zip(orig.iter()) { + assert!(new.x - orig.x == 10.0); + assert!(new.y - orig.y == 10.0); + } + println!("They were modified!"); + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs new file mode 100644 index 0000000..064ca8b --- /dev/null +++ b/lyra-ecs/src/query/entities.rs @@ -0,0 +1,40 @@ +use crate::{world::Entity, archetype::{Archetype, ArchetypeId}}; + +use super::{Fetch, Query}; + +pub struct EntitiesFetch<'a> { + entities: &'a [Entity], +} + +impl<'a> Fetch<'a> for EntitiesFetch<'a> { + type Item = Entity; + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let e = *self.entities.get_unchecked(entity.0 as usize); + e + } + + fn create_empty() -> Self { + Self { + entities: &[], + } + } +} + +pub struct Entities; + +impl Query for Entities { + type Item<'a> = Entity; + + type Fetch<'a> = EntitiesFetch<'a>; + + fn can_visit_archetype(&self, archetype: &Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, arch_id: ArchetypeId, archetype: &'a Archetype) -> Self::Fetch<'a> { + EntitiesFetch { + entities: &archetype.entities, + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs new file mode 100644 index 0000000..9685e59 --- /dev/null +++ b/lyra-ecs/src/query/mod.rs @@ -0,0 +1,152 @@ +use std::{any::{TypeId, Any}, cell::RefCell, rc::Rc, collections::{HashMap, VecDeque}, io::Read, slice::Iter, ops::Range}; + +use crate::{archetype::{Archetype, ComponentColumn, ArchetypeId}, bundle::Bundle, component::Component, world::{Record, EntityId, ArchetypeEntityId}}; + +pub mod view; +pub use view::*; + +pub mod entities; +pub use entities::*; + +pub mod borrow; +pub use borrow::*; + +pub struct QuerySimple<'a> { + //entities: Iter, + type_ids: Vec, +} + +impl<'a> QuerySimple<'a> { + pub fn new(archetypes: Vec<&'a Archetype>) -> Self { + Self { + archetypes, + type_ids: Vec::new(), + } + } + + pub fn with_component(&mut self) { + let type_id = TypeId::of::(); + self.type_ids.push(type_id); + } + + pub fn run(&self) -> impl Iterator + { + //let mut columns = Vec::new(); + + /* for type_id in self.type_ids.iter() { + for arch in self.archetypes.iter() { + if let Some(col) = arch.columns + .iter().find(|c| c.component_type_id() == type_id.clone()) { + + columns.push(col); + } + } + } + + let mut entities: Vec<&C1> = columns.iter().map(|bx| bx.as_any().downcast_ref::() + .expect("Failure to downcast component column")) + .collect(); */ + + let entities = self.archetypes + .iter() + .filter_map(|a| a.get_component_column::()) + .flatten(); + + + /* let columns = self.type_ids + .iter() + .map(|type_id| { + let a = self.archetypes + .iter() + .filter_map(|arch| { + arch.columns + .iter() + .find(|c| c.component_type_id() == type_id.clone()) + }); + }); */ + + /* self.type_ids + .iter() + .map(|type_id| { + self.archetypes + .iter() + .map(|arch| { + arch.get_component_column_typeid(type_id.clone()) + }) + }); */ + + + return entities; + } +} + +/// A [`Fetch`]er implementation gets data out of an archetype. +pub trait Fetch<'a> { + /// The type that this Fetch yields + type Item: 'a; + + /// Returns true if the entity should be visited or skipped. + fn can_visit_item(&mut self, entity: ArchetypeEntityId) -> bool { + let _ = entity; // ignore compiler warning + true + } + + /// Creates an empty Fetch. DO NOT try to `get_item` on this + fn create_empty() -> Self; + + unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item; +} + +pub trait Query { + /// The item that this query yields + type Item<'a>: 'a; + + /// The fetcher used for this query + type Fetch<'a>: Fetch<'a, Item = Self::Item<'a>>; + + /// Returns true if the archetype should be visited or skipped. + fn can_visit_archetype(&self, archetype: &Archetype) -> bool; + + unsafe fn fetch<'a>(&self, arch_id: ArchetypeId, archetype: &'a Archetype) -> Self::Fetch<'a>; +} + +#[cfg(test)] +mod tests { + use crate::{world::World, archetype::Archetype}; + + use super::{View, Entities}; + + #[derive(Clone, Copy, Debug, Default)] + struct Vec2 { + x: f32, + y: f32, + } + + impl Vec2 { + pub fn new(x: f32, y: f32) -> Self { + Self { + x, + y, + } + } + } + + #[test] + fn simple_view() { + let mut world = World::new(); + + world.spawn((Vec2::new(10.0, 50.0),)); + world.spawn((Vec2::new(25.0, 30.0),)); + + let entities = Entities {}; + + let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); + + let v = View::new(entities, archetypes); + + for e in v.into_iter() { + println!("Got entity! {:?}", e); + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs new file mode 100644 index 0000000..9fa5323 --- /dev/null +++ b/lyra-ecs/src/query/view.rs @@ -0,0 +1,89 @@ +use std::ops::Range; + +use crate::{archetype::{Archetype, ArchetypeId}, world::ArchetypeEntityId}; + +use super::{Query, Fetch}; + +pub struct View<'a, Q: Query> { + query: Q, + archetypes: Vec<&'a Archetype>, +} + +impl<'a, Q> View<'a, Q> +where + Q: Query, +{ + pub fn new(query: Q, archetypes: Vec<&'a Archetype>) -> Self { + Self { + query, + archetypes, + } + } +} + +impl<'a, Q> IntoIterator for View<'a, Q> +where + Q: Query, +{ + type Item = Q::Item<'a>; + + type IntoIter = ViewIter<'a, Q>; + + fn into_iter(self) -> Self::IntoIter { + ViewIter { + query: self.query, + fetcher: Q::Fetch::create_empty(), + archetypes: self.archetypes, + next_archetype: 0, + component_indices: 0..0, + } + } +} + +pub struct ViewIter<'a, Q: Query> { + query: Q, + fetcher: Q::Fetch<'a>, + archetypes: Vec<&'a Archetype>, + next_archetype: usize, + component_indices: Range, +} + +impl<'a, Q> Iterator for ViewIter<'a, Q> +where + Q: Query, +{ + type Item = Q::Item<'a>; + + fn next(&mut self) -> Option { + loop { + if let Some(entity_index) = self.component_indices.next() { + let entity_index = ArchetypeEntityId(entity_index); + if !self.fetcher.can_visit_item(entity_index) { + continue; + } else { + let i = unsafe { self.fetcher.get_item(entity_index) }; + return Some(i); + } + } 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.query.can_visit_archetype(arch) { + continue; + } + + self.fetcher = unsafe { self.query.fetch(ArchetypeId(arch_id as u64), arch) }; + self.component_indices = 0..arch.entities.len() as u64; + } + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 68f3946..6830261 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,7 +1,7 @@ use std::{collections::{HashMap, VecDeque}, any::{Any, TypeId}}; use std::slice::Iter; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component}; +use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::QuerySimple}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EntityId(pub u64); @@ -11,18 +11,19 @@ pub struct EntityId(pub u64); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ArchetypeEntityId(pub u64); +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Entity { id: EntityId, generation: u64, } -struct Record { +pub struct Record { id: ArchetypeId, index: ArchetypeEntityId, } pub struct World { - archetypes: HashMap, + pub(crate) archetypes: HashMap, next_archetype_id: ArchetypeId, entity_index: HashMap, dead_entities: VecDeque, @@ -68,9 +69,7 @@ impl World { .find(|a| a.is_archetype_for(bundle_types.clone())); if let Some(archetype) = archetype { - // take components from the bundle and add it to the archetype - let columns = bundle.take_components(); - let arche_idx = archetype.add_entity(columns); + let arche_idx = archetype.add_entity(new_entity, bundle); // Create entity record and store it let record = Record { @@ -82,11 +81,10 @@ impl World { } // create a new archetype if one isn't found else { - let columns = bundle.take_components(); - // create archetype let new_arch_id = self.next_archetype_id.increment(); - let archetype = Archetype::from_columns(new_arch_id, columns); + let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); + let entity_arch_id = archetype.add_entity(new_entity, bundle); // store archetype self.archetypes.insert(new_arch_id, archetype); @@ -95,7 +93,7 @@ impl World { let record = Record { id: new_arch_id, // this is the first entity in the archetype - index: ArchetypeEntityId(0), + index: entity_arch_id, }; self.entity_index.insert(new_entity.id, record); @@ -108,7 +106,7 @@ impl World { let record = self.entity_index.get(&entity.id)?; let archetype = self.archetypes.get(&record.id)?; - archetype.get_component(record.index) + archetype.get_entity_component(record.index) } pub fn query(&self) -> impl Iterator { @@ -118,10 +116,34 @@ impl World { .flatten() } + pub fn query_better(&self) -> QuerySimple { + let archetypes = self.archetypes.values().collect(); + QuerySimple::new(archetypes) + } + /* pub fn query_m(&self) -> impl Iterator { self.archetypes .iter() .filter_map(|(_, a)| a.get_component_column::()) .flatten() } */ +} + +#[cfg(test)] +mod tests { + use super::World; + + struct Vec2 { + x: f32, + y: f32, + } + + #[test] + fn spawning_entity() { + let mut world = World::new(); + let _e = world.spawn((Vec2 { + x: 10.0, + y: 15.0, + }, )); + } } \ No newline at end of file