From 0f062217cacd1b80b4186296673259babfb45c32 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 4 Jan 2024 20:49:27 -0500 Subject: [PATCH] ecs: Add world query, make it possible to request resources from function systems --- lyra-ecs/src/query/borrow.rs | 16 ++-- lyra-ecs/src/query/mod.rs | 6 +- lyra-ecs/src/query/resource.rs | 61 +++++++++++--- lyra-ecs/src/query/view.rs | 18 ++--- lyra-ecs/src/query/world.rs | 140 +++++++++++++++++++++++++++++++++ lyra-ecs/src/resource.rs | 4 +- lyra-ecs/src/system/graph.rs | 8 +- lyra-ecs/src/system/mod.rs | 12 +-- lyra-ecs/src/world.rs | 2 +- 9 files changed, 226 insertions(+), 41 deletions(-) create mode 100644 lyra-ecs/src/query/world.rs diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index 059a889..4ee6739 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -1,6 +1,6 @@ use std::{marker::PhantomData, ptr::NonNull, cell::{Ref, RefMut}}; -use crate::{world::World, ComponentColumn, DynTypeId, Tick}; +use crate::{world::World, ComponentColumn, DynTypeId, Tick, Component}; use super::{Fetch, Query, AsQuery}; @@ -97,11 +97,11 @@ where } } -impl AsQuery for QueryBorrow { +impl AsQuery for QueryBorrow { type Query = Self; } -impl AsQuery for &T { +impl AsQuery for &T { type Query = QueryBorrow; } @@ -209,11 +209,11 @@ where } } -impl AsQuery for QueryBorrowMut { +impl AsQuery for QueryBorrowMut { type Query = Self; } -impl AsQuery for &mut T { +impl AsQuery for &mut T { type Query = QueryBorrowMut; } @@ -244,7 +244,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::new(&world, borrow, archetypes); + let v = View::>::new(&world, borrow, archetypes); for e in v.into_iter() { println!("Found entity at {:?}", e); @@ -260,7 +260,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::new(&world, borrow, archetypes); + let v = View::>::new(&world, borrow, archetypes); let mut orig = vec![]; @@ -277,7 +277,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::new(&world, borrow, archetypes); + let v = View::>::new(&world, borrow, archetypes); for (new, orig) in v.into_iter().zip(orig.iter()) { assert!(new.x - orig.x == 10.0); diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index 1149fbc..842763c 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -23,6 +23,10 @@ pub mod tick; #[allow(unused_imports)] pub use tick::*; +pub mod world; +#[allow(unused_imports)] +pub use world::*; + pub mod dynamic; /// A [`Fetch`]er implementation gets data out of an archetype. @@ -100,7 +104,7 @@ mod tests { let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::new(&world, entities, archetypes); + let v = View::::new(&world, entities, archetypes); for e in v.into_iter() { println!("Got entity! {:?}", e); diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index dcba6ea..cee462f 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -10,7 +10,7 @@ pub struct FetchResource<'a, T> { } impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { - type Item = Ref<'a, T>; + type Item = Res<'a, T>; fn dangling() -> Self { Self { @@ -25,7 +25,7 @@ impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { let w = self.world.unwrap(); - w.get_resource::() + Res(w.get_resource::()) } } @@ -34,7 +34,7 @@ impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { pub struct QueryResource { _phantom: PhantomData } -pub type Resource = QueryResource; +//pub type Resource = QueryResource; impl Copy for QueryResource {} @@ -45,7 +45,7 @@ impl Clone for QueryResource { } impl Query for QueryResource { - type Item<'a> = Ref<'a, T>; + type Item<'a> = Res<'a, T>; type Fetch<'a> = FetchResource<'a, T>; @@ -78,13 +78,28 @@ impl AsQuery for QueryResource { type Query = QueryResource; } +/// A struct used for querying resources from the World. +pub struct Res<'a, T>(Ref<'a, T>); + +impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl<'a, T: ResourceObject> AsQuery for Res<'a, T> { + type Query = QueryResource; +} + pub struct FetchResourceMut<'a, T> { world: Option<&'a World>, _phantom: PhantomData, } impl<'a, T: 'a + 'static> Fetch<'a> for FetchResourceMut<'a, T> { - type Item = RefMut<'a, T>; + type Item = ResMut<'a, T>; fn dangling() -> Self { Self { @@ -99,7 +114,7 @@ impl<'a, T: 'a + 'static> Fetch<'a> for FetchResourceMut<'a, T> { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { let w = self.world.unwrap(); - w.get_resource_mut::() + ResMut(w.get_resource_mut::()) } } @@ -109,8 +124,6 @@ pub struct QueryResourceMut { _phantom: PhantomData } -pub type ResourceMut = QueryResourceMut; - impl Copy for QueryResourceMut {} impl Clone for QueryResourceMut { @@ -120,7 +133,7 @@ impl Clone for QueryResourceMut { } impl Query for QueryResourceMut { - type Item<'a> = RefMut<'a, T>; + type Item<'a> = ResMut<'a, T>; type Fetch<'a> = FetchResourceMut<'a, T>; @@ -152,8 +165,33 @@ impl Query for QueryResourceMut { impl AsQuery for QueryResourceMut { type Query = QueryResourceMut; } + +/// A struct used for querying resources from the World. +pub struct ResMut<'a, T>(RefMut<'a, T>); + +impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> { + + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + +impl<'a, T: ResourceObject> AsQuery for ResMut<'a, T> { + type Query = QueryResourceMut; +} + #[cfg(test)] mod tests { + use std::ops::{Deref, DerefMut}; + use crate::{world::World, tests::{Vec2, Vec3}, query::QueryResourceMut}; use super::QueryResource; @@ -171,6 +209,7 @@ mod tests { let mut res_iter = world.view_iter::>(); let res = res_iter.next().unwrap(); + let res = res.deref(); assert_eq!(res.0, 0); } @@ -195,7 +234,7 @@ mod tests { assert_eq!(i.count(), 3); for (res, e) in world.view_iter::<(QueryResource, &Vec2)>() { - println!("Got res {}! and entity at {:?}", res.0, e); + println!("Got res {}! and entity at {:?}", res.deref().0, e); } let i = world.view_iter::>(); @@ -214,12 +253,14 @@ mod tests { { let mut resmut_iter = world.view_iter::>(); let mut resmut = resmut_iter.next().unwrap(); + let resmut = resmut.deref_mut(); assert_eq!(resmut.0, 0); resmut.0 += 20; } let mut res_iter = world.view_iter::>(); let res = res_iter.next().unwrap(); + let res = res.deref(); assert_eq!(res.0, 20); } } \ No newline at end of file diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 3377efb..43442d6 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -2,19 +2,19 @@ use std::ops::Range; use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick}; -use super::{Query, Fetch}; +use super::{Query, Fetch, AsQuery}; -pub struct View<'a, Q: Query> { +pub struct View<'a, Q: AsQuery> { world: &'a World, - query: Q, + query: Q::Query, archetypes: Vec<&'a Archetype>, } impl<'a, Q> View<'a, Q> where - Q: Query, + Q: AsQuery, { - pub fn new(world: &'a World, query: Q, archetypes: Vec<&'a Archetype>) -> Self { + pub fn new(world: &'a World, query: Q::Query, archetypes: Vec<&'a Archetype>) -> Self { Self { world, query, @@ -25,14 +25,14 @@ where impl<'a, Q> IntoIterator for View<'a, Q> where - Q: Query, + Q: AsQuery, { - type Item = Q::Item<'a>; + type Item = ::Item<'a>; - type IntoIter = ViewIter<'a, Q>; + type IntoIter = ViewIter<'a, Q::Query>; fn into_iter(self) -> Self::IntoIter { - let tick = self.world.tick_tracker().tick_when(Q::MUTATES); + let tick = self.world.tick_tracker().tick_when(Q::Query::MUTATES); ViewIter { world: self.world, diff --git a/lyra-ecs/src/query/world.rs b/lyra-ecs/src/query/world.rs new file mode 100644 index 0000000..de15f98 --- /dev/null +++ b/lyra-ecs/src/query/world.rs @@ -0,0 +1,140 @@ +use std::ptr::NonNull; + +use crate::World; + +use super::{Fetch, Query, AsQuery}; + +pub struct FetchWorldRef<'a> { + world: &'a World +} + +impl<'a> Fetch<'a> for FetchWorldRef<'a> { + type Item = &'a World; + + fn dangling() -> Self { + unreachable!() + } + + fn can_visit_item(&mut self, _entity: crate::ArchetypeEntityId) -> bool { + true + } + + unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { + self.world + } +} + +#[derive(Clone, Copy)] +pub struct QueryWorldRef; + +impl Default for QueryWorldRef { + fn default() -> Self { + Self + } +} + +impl Query for QueryWorldRef { + type Item<'a> = &'a World; + + type Fetch<'a> = FetchWorldRef<'a>; + + const ALWAYS_FETCHES: bool = true; + + fn new() -> Self { + QueryWorldRef + } + + fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> { + FetchWorldRef { + world + } + } + + unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option> { + Some(FetchWorldRef { + world, + }) + } +} + +impl AsQuery for QueryWorldRef { + type Query = Self; +} + +impl AsQuery for &World { + type Query = QueryWorldRef; +} + +pub struct FetchWorldRefMut<'a> { + world: &'a mut World +} + +impl<'a> Fetch<'a> for FetchWorldRefMut<'a> { + type Item = &'a mut World; + + fn dangling() -> Self { + unreachable!() + } + + fn can_visit_item(&mut self, _entity: crate::ArchetypeEntityId) -> bool { + true + } + + unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { + // safety: world outlives the fetcher + let mut world_ptr = NonNull::from(&self.world); + unsafe { world_ptr.as_mut() } + } +} + +#[derive(Clone, Copy)] +pub struct QueryWorldRefMut; + +impl Default for QueryWorldRefMut { + fn default() -> Self { + Self + } +} + +impl Query for QueryWorldRefMut { + type Item<'a> = &'a mut World; + + type Fetch<'a> = FetchWorldRefMut<'a>; + + const ALWAYS_FETCHES: bool = true; + + fn new() -> Self { + QueryWorldRefMut + } + + fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> { + self.fetch_world(world).unwrap() + } + + unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option> { + // safety: the queries are expected to be executed by an executor that would + // not cause multiple mutable borrows + let mut world_ptr = NonNull::from(world); + let world = unsafe { world_ptr.as_mut() }; + + Some(FetchWorldRefMut { + world, + }) + } +} + +impl AsQuery for QueryWorldRefMut { + type Query = Self; +} + +impl AsQuery for &mut World { + type Query = QueryWorldRefMut; +} \ No newline at end of file diff --git a/lyra-ecs/src/resource.rs b/lyra-ecs/src/resource.rs index 55dd47b..019067b 100644 --- a/lyra-ecs/src/resource.rs +++ b/lyra-ecs/src/resource.rs @@ -1,8 +1,8 @@ use std::{any::{TypeId, Any}, cell::{RefCell, Ref, RefMut}}; /// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. -pub trait ResourceObject: Send + Sync + 'static {} -impl ResourceObject for T {} +pub trait ResourceObject: 'static {} +impl ResourceObject for T {} /// A type erased storage for a Resource. pub struct ResourceData { diff --git a/lyra-ecs/src/system/graph.rs b/lyra-ecs/src/system/graph.rs index b7150ca..4612fba 100644 --- a/lyra-ecs/src/system/graph.rs +++ b/lyra-ecs/src/system/graph.rs @@ -108,7 +108,7 @@ impl GraphExecutor { mod tests { use std::ptr::NonNull; - use crate::{world::World, query::{ResourceMut, View}, system::IntoSystem}; + use crate::{world::World, query::{ResMut, View}, system::IntoSystem}; use super::GraphExecutor; @@ -120,7 +120,7 @@ mod tests { let mut exec = GraphExecutor::new(); - let a_system = |view: View>>| -> anyhow::Result<()> { + let a_system = |view: View>>| -> anyhow::Result<()> { println!("System 'a' ran!"); let mut order = view.into_iter().next().unwrap(); @@ -129,7 +129,7 @@ mod tests { Ok(()) }; - let b_system = |view: View>>| -> anyhow::Result<()> { + let b_system = |view: View>>| -> anyhow::Result<()> { println!("System 'b' ran!"); let mut order = view.into_iter().next().unwrap(); @@ -138,7 +138,7 @@ mod tests { Ok(()) }; - let c_system = |view: View>>| -> anyhow::Result<()> { + let c_system = |view: View>>| -> anyhow::Result<()> { println!("System 'c' ran!"); let mut order = view.into_iter().next().unwrap(); diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs index e8386ff..074d9f2 100644 --- a/lyra-ecs/src/system/mod.rs +++ b/lyra-ecs/src/system/mod.rs @@ -1,6 +1,6 @@ use std::{ptr::NonNull, marker::PhantomData, cell::{Ref, RefMut}}; -use crate::{world::World, Access, ResourceObject, query::{Query, View}}; +use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery}}; pub mod graph; pub use graph::*; @@ -131,20 +131,20 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O } impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } /// An ArgFetcher implementation for query [`View`]s -pub struct ViewArgFetcher { - query: Q +pub struct ViewArgFetcher { + query: Q::Query } -impl<'a, Q: Query> FnArg for View<'a, Q> { +impl<'a, Q: AsQuery> FnArg for View<'a, Q> { type Fetcher = ViewArgFetcher; } -impl FnArgFetcher for ViewArgFetcher { +impl FnArgFetcher for ViewArgFetcher { type Arg<'a> = View<'a, Q>; fn new() -> Self { ViewArgFetcher { - query: Q::new(), + query: ::new(), } } diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 697f74f..e31b70e 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -213,7 +213,7 @@ impl World { /// View into the world for a set of entities that satisfy the queries. pub fn view_iter(&self) -> ViewIter { let archetypes = self.archetypes.values().collect(); - let v = View::new(self, T::Query::new(), archetypes); + let v = View::::new(self, T::Query::new(), archetypes); v.into_iter() }