From 6fedb270b9cad1b19be2dabdd1d7ef025605be13 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 30 Nov 2023 23:05:06 -0500 Subject: [PATCH] Convert package to a lib, get resources from views --- lyra-ecs/.vscode/launch.json | 2 + lyra-ecs/README.md | 2 +- lyra-ecs/src/lib.rs | 25 +++++++ lyra-ecs/src/main.rs | 22 ------ lyra-ecs/src/query/borrow.rs | 8 +-- lyra-ecs/src/query/mod.rs | 25 +++++-- lyra-ecs/src/query/resource.rs | 126 +++++++++++++++++++++++++++++++++ lyra-ecs/src/query/view.rs | 25 +++++-- lyra-ecs/src/resource.rs | 4 +- lyra-ecs/src/tests.rs | 11 +++ 10 files changed, 210 insertions(+), 40 deletions(-) create mode 100644 lyra-ecs/src/lib.rs delete mode 100644 lyra-ecs/src/main.rs create mode 100644 lyra-ecs/src/query/resource.rs diff --git a/lyra-ecs/.vscode/launch.json b/lyra-ecs/.vscode/launch.json index 97f385b..8c4260f 100644 --- a/lyra-ecs/.vscode/launch.json +++ b/lyra-ecs/.vscode/launch.json @@ -32,7 +32,9 @@ "--no-run", "--bin=lyra-ecs", "--package=lyra-ecs", + "query::resource::tests::query", "--", + "--exact", "--nocapture" ], "filter": { diff --git a/lyra-ecs/README.md b/lyra-ecs/README.md index 7d84054..ff5c818 100644 --- a/lyra-ecs/README.md +++ b/lyra-ecs/README.md @@ -19,7 +19,7 @@ I couldn't find anything that fulfilled my needs, specifically an ECS that can s - [ ] Mutable views - [ ] Make it possible so that ONLY `ViewMut` can borrow mutably - [x] Resources -- [ ] Get resources in views somehow +- [x] Get resources in views somehow - [ ] Relationships (maybe this can be done through queries, idk) - [ ] Dynamic queries * Needed for scripting engines that can create their own components that Rust does not know the types of. diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs new file mode 100644 index 0000000..96e8783 --- /dev/null +++ b/lyra-ecs/src/lib.rs @@ -0,0 +1,25 @@ +use crate::world::World; + +mod archetype; +pub use archetype::*; + +pub mod world; +pub use world::*; + +mod bundle; +pub use bundle::*; + +mod component; +pub use component::*; + +mod query; +pub use query::*; + +mod component_info; +pub use component_info::*; + +mod resource; +pub use resource::*; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/lyra-ecs/src/main.rs b/lyra-ecs/src/main.rs deleted file mode 100644 index fc9dd56..0000000 --- a/lyra-ecs/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::world::World; - -mod archetype; -mod world; -mod bundle; -mod component; -mod query; -mod component_info; -mod resource; - -#[cfg(test)] -mod tests; - -#[derive(Debug)] -pub struct Position2d(i32, i32); - -fn main() { - let mut world = World::new(); - - let pos = Position2d(836, 348); - let _e = world.spawn((pos,)); -} diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index 23dcf9c..433eda0 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -37,9 +37,9 @@ where /// A Query for borrowing components from archetypes. /// /// Since [`AsQuery`] is implemented for `&T`, you can use this query like this: -/// ```rust +/// ```nobuild /// for ts in world.view::<&T>() { -/// println!("Got an &T!"); +/// println!("Got a &T!"); /// } /// ``` pub struct QueryBorrow { @@ -134,9 +134,9 @@ where /// A Query for mutably borrowing components from archetypes. /// /// Since [`AsQuery`] is implemented for `&mut T`, you can use this query like this: -/// ```rust +/// ```nobuild /// for ts in world.view::<&mut T>() { -/// println!("Got an &T!"); +/// println!("Got an &mut T!"); /// } /// ``` pub struct QueryBorrowMut { diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index f2a92e3..a6f0c11 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -15,6 +15,10 @@ pub mod tuple; #[allow(unused_imports)] pub use tuple::*; +pub mod resource; +#[allow(unused_imports)] +pub use resource::*; + /// A [`Fetch`]er implementation gets data out of an archetype. pub trait Fetch<'a> { /// The type that this Fetch yields @@ -39,16 +43,25 @@ pub trait Query { /// The fetcher used for this query type Fetch<'a>: Fetch<'a, Item = Self::Item<'a>>; + /// A constant that signifies if this Query will always fetch something. + /// [`QueryResource`] has this set to true, since they will almost always fetch + /// something. + /// + /// [`View`] uses this to determine if it should continue to iterate this Query. + const ALWAYS_FETCHES: bool = false; + /// Returns true if the archetype should be visited or skipped. fn can_visit_archetype(&self, archetype: &Archetype) -> bool; - /// Returns true if the Query can visit the world - /* fn can_visit_world(&self, world: &'a World) -> bool { - let _ = world; // compiler warnings - false - } */ - unsafe fn fetch<'a>(&self, world: &'a World, arch_id: ArchetypeId, archetype: &'a Archetype) -> Self::Fetch<'a>; + + /// Attempt to fetch only from the world. + /// + /// This is used in [`QueryResource`]. + unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option> { + let _ = world; + None + } } /// A trait for getting the query of a type. diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs new file mode 100644 index 0000000..d73e5fd --- /dev/null +++ b/lyra-ecs/src/query/resource.rs @@ -0,0 +1,126 @@ +use std::{marker::PhantomData, any::TypeId, ptr::NonNull, cell::Ref}; + +use crate::{world::World, resource::ResourceObject}; + +use super::{Query, Fetch, AsQuery, DefaultQuery}; + +pub struct FetchResource<'a, T> { + world: Option<&'a World>, + _phantom: PhantomData, +} + +impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { + type Item = Ref<'a, T>; + + fn dangling() -> Self { + Self { + world: None, + _phantom: PhantomData, + } + } + + fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool { + true + } + + unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { + let w = self.world.unwrap(); + w.get_resource::() + } + +} + +/// A query type for getting Resources +/// +/// Resources are stored in an archetype with the id of [`world::RESOURCE_ARCHETYPE_ID`]. There is only one instance of a type of resource. +/// The resources are stored in that archetype in the first entity in the column. +pub struct QueryResource { + _phantom: PhantomData +} + +pub type Resource = QueryResource; + +impl Query for QueryResource { + type Item<'a> = Ref<'a, T>; + + type Fetch<'a> = FetchResource<'a, T>; + + const ALWAYS_FETCHES: bool = true; + + fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, world: &'a World, _arch_id: crate::archetype::ArchetypeId, _archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + self.fetch_world(world).unwrap() + } + + unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option> { + Some(FetchResource { + world: Some(world), + _phantom: PhantomData, + }) + } +} + +impl AsQuery for QueryResource { + type Query = QueryResource; +} + +impl DefaultQuery for QueryResource { + fn default_query() -> Self::Query { + QueryResource:: { + _phantom: PhantomData + } + } +} +#[cfg(test)] +mod tests { + use crate::{world::World, tests::{Vec2, Vec3}}; + + use super::QueryResource; + + struct SomeCounter(u32); + + #[test] + fn simple_query() { + let mut world = World::new(); + { + let counter = SomeCounter(0); + world.add_resource(counter); + println!("Added resource"); + } + + let mut res_iter = world.view::>(); + let res = res_iter.next().unwrap(); + assert_eq!(res.0, 0); + } + + #[test] + fn complex_query() { + let mut world = World::new(); + { + let counter = SomeCounter(0); + + world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),)); + world.spawn((Vec3::rand(),)); + + world.add_resource(counter); + println!("Added resource"); + } + + let i = world.view::<(QueryResource, &Vec2)>(); + assert_eq!(i.count(), 3); + let i = world.view::<(&Vec2, QueryResource)>(); + assert_eq!(i.count(), 3); + + for (res, e) in world.view::<(QueryResource, &Vec2)>() { + println!("Got res {}! and entity at {:?}", res.0, e); + } + + let i = world.view::>(); + assert_eq!(i.count(), 1); + } +} \ No newline at end of file diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 79b6fd6..1dabfbd 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -35,7 +35,7 @@ where ViewIter { world: self.world, query: self.query, - fetcher: Q::Fetch::dangling(), + fetcher: None, archetypes: self.archetypes, next_archetype: 0, component_indices: 0..0, @@ -46,7 +46,7 @@ where pub struct ViewIter<'a, Q: Query> { world: &'a World, query: Q, - fetcher: Q::Fetch<'a>, + fetcher: Option>, archetypes: Vec<&'a Archetype>, next_archetype: usize, component_indices: Range, @@ -60,12 +60,27 @@ where fn next(&mut self) -> Option { loop { + if Q::ALWAYS_FETCHES { + // only fetch this query once. + // fetcher gets set to Some after this `next` call. + if self.fetcher.is_none() { + if let Some(mut fetch) = unsafe { self.query.fetch_world(self.world) } { + let res = unsafe { Some(fetch.get_item(ArchetypeEntityId(0))) }; + self.fetcher = Some(fetch); + return res; + } + } else { + return None; + } + } + if let Some(entity_index) = self.component_indices.next() { + let fetcher = self.fetcher.as_mut().unwrap(); let entity_index = ArchetypeEntityId(entity_index); - if !self.fetcher.can_visit_item(entity_index) { + if !fetcher.can_visit_item(entity_index) { continue; } else { - let i = unsafe { self.fetcher.get_item(entity_index) }; + let i = unsafe { fetcher.get_item(entity_index) }; return Some(i); } } else { @@ -85,7 +100,7 @@ where continue; } - self.fetcher = unsafe { self.query.fetch(self.world, ArchetypeId(arch_id as u64), arch) }; + self.fetcher = unsafe { Some(self.query.fetch(self.world, ArchetypeId(arch_id as u64), arch)) }; self.component_indices = 0..arch.entities.len() as u64; } } diff --git a/lyra-ecs/src/resource.rs b/lyra-ecs/src/resource.rs index 20130c0..71097f3 100644 --- a/lyra-ecs/src/resource.rs +++ b/lyra-ecs/src/resource.rs @@ -1,8 +1,8 @@ use std::{any::TypeId, alloc::Layout, cell::{RefCell, Ref, RefMut}, ptr::NonNull, alloc}; /// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. -/* pub trait Resource: Send + Sync + 'static {} -impl Resource for T {} */ +pub trait ResourceObject: Send + Sync + 'static {} +impl ResourceObject for T {} /// A type erased storage for a Resource. /// diff --git a/lyra-ecs/src/tests.rs b/lyra-ecs/src/tests.rs index b03fc6c..aa33648 100644 --- a/lyra-ecs/src/tests.rs +++ b/lyra-ecs/src/tests.rs @@ -42,4 +42,15 @@ impl Vec3 { z } } + + pub fn rand() -> Self { + let mut rng = rand::thread_rng(); + let range = 30.0..1853.0; + + Vec3 { + x: rng.gen_range(range.clone()), + y: rng.gen_range(range.clone()), + z: rng.gen_range(range) + } + } } \ No newline at end of file