From 9c6c32199d6a961f84b76e02bdb9897307100c21 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 3 Dec 2023 23:14:27 -0500 Subject: [PATCH] Create simple function systems --- lyra-ecs/.vscode/launch.json | 3 +- lyra-ecs/Cargo.lock | 7 ++ lyra-ecs/Cargo.toml | 3 +- lyra-ecs/src/lib.rs | 11 ++- lyra-ecs/src/query/borrow.rs | 50 +++++------ lyra-ecs/src/query/entities.rs | 5 ++ lyra-ecs/src/query/mod.rs | 8 +- lyra-ecs/src/query/resource.rs | 28 +++--- lyra-ecs/src/query/tuple.rs | 26 ++---- lyra-ecs/src/system/mod.rs | 156 +++++++++++++++++++++++++++++++++ lyra-ecs/src/world.rs | 6 +- 11 files changed, 238 insertions(+), 65 deletions(-) create mode 100644 lyra-ecs/src/system/mod.rs diff --git a/lyra-ecs/.vscode/launch.json b/lyra-ecs/.vscode/launch.json index 8c4260f..8facf6e 100644 --- a/lyra-ecs/.vscode/launch.json +++ b/lyra-ecs/.vscode/launch.json @@ -30,7 +30,6 @@ "args": [ "test", "--no-run", - "--bin=lyra-ecs", "--package=lyra-ecs", "query::resource::tests::query", "--", @@ -39,7 +38,7 @@ ], "filter": { "name": "lyra-ecs", - "kind": "bin" + "kind": "lib" } }, "args": [], diff --git a/lyra-ecs/Cargo.lock b/lyra-ecs/Cargo.lock index e3c6de7..587ab0a 100644 --- a/lyra-ecs/Cargo.lock +++ b/lyra-ecs/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "cfg-if" version = "1.0.0" @@ -29,6 +35,7 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" name = "lyra-ecs" version = "0.1.0" dependencies = [ + "anyhow", "rand", ] diff --git a/lyra-ecs/Cargo.toml b/lyra-ecs/Cargo.toml index 7349d80..d913889 100644 --- a/lyra-ecs/Cargo.toml +++ b/lyra-ecs/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.75" [dev-dependencies] -rand = "0.8.5" # used for tests \ No newline at end of file +rand = "0.8.5" # used for tests diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs index 96e8783..f1c21fc 100644 --- a/lyra-ecs/src/lib.rs +++ b/lyra-ecs/src/lib.rs @@ -21,5 +21,14 @@ pub use component_info::*; mod resource; pub use resource::*; +mod system; +pub use system::*; + #[cfg(test)] -mod tests; \ No newline at end of file +mod tests; + +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum Access { + Read, + Write, +} \ No newline at end of file diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index edea8f1..9104034 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -2,7 +2,7 @@ use std::{marker::PhantomData, any::TypeId, ptr::NonNull, cell::{Ref, RefCell, R use crate::{world::World, ComponentColumn}; -use super::{Fetch, Query, AsQuery, DefaultQuery}; +use super::{Fetch, Query, AsQuery}; /// Fetcher for borrowing components from archetypes. pub struct FetchBorrow<'a, T> { @@ -45,6 +45,14 @@ pub struct QueryBorrow { _phantom: PhantomData } +impl Copy for QueryBorrow {} + +impl Clone for QueryBorrow { + fn clone(&self) -> Self { + *self + } +} + impl QueryBorrow { pub fn new() -> Self { Self { @@ -62,6 +70,10 @@ where type Fetch<'a> = FetchBorrow<'a, T>; + fn new() -> Self { + QueryBorrow::::new() + } + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { archetype.columns.iter().any(|c| c.info.type_id == self.type_id) } @@ -82,22 +94,10 @@ impl AsQuery for QueryBorrow { type Query = Self; } -impl DefaultQuery for QueryBorrow { - fn default_query() -> Self { - QueryBorrow::::new() - } -} - impl AsQuery for &T { type Query = QueryBorrow; } -impl DefaultQuery for &T { - fn default_query() -> QueryBorrow { - QueryBorrow::::new() - } -} - /// A fetcher for mutably borrowing components from archetypes. pub struct FetchBorrowMut<'a, T> { col: &'a ComponentColumn, @@ -139,6 +139,14 @@ pub struct QueryBorrowMut { _phantom: PhantomData } +impl Copy for QueryBorrowMut {} + +impl Clone for QueryBorrowMut { + fn clone(&self) -> Self { + *self + } +} + impl QueryBorrowMut { pub fn new() -> Self { Self { @@ -156,6 +164,10 @@ where type Fetch<'a> = FetchBorrowMut<'a, T>; + fn new() -> Self { + QueryBorrowMut::::new() + } + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { archetype.columns.iter().any(|c| c.info.type_id == self.type_id) } @@ -176,22 +188,10 @@ impl AsQuery for QueryBorrowMut { type Query = Self; } -impl DefaultQuery for QueryBorrowMut { - fn default_query() -> Self { - QueryBorrowMut::::new() - } -} - impl AsQuery for &mut T { type Query = QueryBorrowMut; } -impl DefaultQuery for &mut T { - fn default_query() -> QueryBorrowMut { - QueryBorrowMut::::new() - } -} - #[cfg(test)] mod tests { use std::{any::TypeId, mem::size_of, marker::PhantomData}; diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index 122d052..b13502f 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -21,6 +21,7 @@ impl<'a> Fetch<'a> for EntitiesFetch { } } +#[derive(Copy, Clone, Default)] pub struct Entities; impl Query for Entities { @@ -28,6 +29,10 @@ impl Query for Entities { type Fetch<'a> = EntitiesFetch; + fn new() -> Self { + Entities + } + fn can_visit_archetype(&self, archetype: &Archetype) -> bool { let _ = archetype; // ignore unused warnings true diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index a6f0c11..b007897 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -36,7 +36,7 @@ pub trait Fetch<'a> { unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item; } -pub trait Query { +pub trait Query: Copy { /// The item that this query yields type Item<'a>: 'a; @@ -50,6 +50,8 @@ pub trait Query { /// [`View`] uses this to determine if it should continue to iterate this Query. const ALWAYS_FETCHES: bool = false; + fn new() -> Self; + /// Returns true if the archetype should be visited or skipped. fn can_visit_archetype(&self, archetype: &Archetype) -> bool; @@ -74,10 +76,6 @@ pub trait IntoQuery { fn into_query(self) -> Self; } -pub trait DefaultQuery: AsQuery { - fn default_query() -> Self::Query; -} - #[cfg(test)] mod tests { use crate::{world::World, archetype::Archetype, tests::Vec2}; diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index d73e5fd..a0c19da 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -2,7 +2,7 @@ use std::{marker::PhantomData, any::TypeId, ptr::NonNull, cell::Ref}; use crate::{world::World, resource::ResourceObject}; -use super::{Query, Fetch, AsQuery, DefaultQuery}; +use super::{Query, Fetch, AsQuery}; pub struct FetchResource<'a, T> { world: Option<&'a World>, @@ -34,19 +34,33 @@ impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> { /// /// 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 { +pub struct QueryResource { _phantom: PhantomData } +impl Copy for QueryResource {} + +impl Clone for QueryResource { + fn clone(&self) -> Self { + *self + } +} + pub type Resource = QueryResource; -impl Query for QueryResource { +impl Query for QueryResource { type Item<'a> = Ref<'a, T>; type Fetch<'a> = FetchResource<'a, T>; const ALWAYS_FETCHES: bool = true; + fn new() -> Self { + QueryResource:: { + _phantom: PhantomData + } + } + fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool { true } @@ -66,14 +80,6 @@ impl Query for QueryResource { 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}}; diff --git a/lyra-ecs/src/query/tuple.rs b/lyra-ecs/src/query/tuple.rs index d3beb47..99a3682 100644 --- a/lyra-ecs/src/query/tuple.rs +++ b/lyra-ecs/src/query/tuple.rs @@ -1,6 +1,6 @@ use crate::world::World; -use super::{Query, Fetch, AsQuery, DefaultQuery}; +use super::{Query, Fetch, AsQuery}; // Technically all of these implementations for a 2-sized tuple // can be implemented by the macro near the end of the file, but @@ -37,6 +37,10 @@ where type Fetch<'a> = (Q1::Fetch<'a>, Q2::Fetch<'a>); + fn new() -> Self { + (Q1::new(), Q2::new()) + } + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { let (q1, q2) = self; q1.can_visit_archetype(archetype) && q2.can_visit_archetype(archetype) @@ -56,16 +60,6 @@ where type Query = (Q1::Query, Q2::Query); } -impl DefaultQuery for (Q1, Q2) -where - Q1: DefaultQuery, - Q2: DefaultQuery, -{ - fn default_query() -> (Q1::Query, Q2::Query) { - ( Q1::default_query(), Q2::default_query() ) - } -} - macro_rules! impl_bundle_tuple { ( $($name: ident),+ ) => ( #[allow(non_snake_case)] @@ -95,6 +89,10 @@ macro_rules! impl_bundle_tuple { type Item<'a> = ($($name::Item<'a>,)+); type Fetch<'a> = ($($name::Fetch<'a>,)+); + fn new() -> Self { + ( $($name::new(),)+ ) + } + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { let ( $($name,)+ ) = self; @@ -112,12 +110,6 @@ macro_rules! impl_bundle_tuple { impl<$($name: AsQuery),+> AsQuery for ($($name,)+) { type Query = ($($name::Query,)+); } - - impl<$($name: DefaultQuery),+> DefaultQuery for ($($name,)+) { - fn default_query() -> ($($name::Query,)+) { - ( $($name::default_query(),)+ ) - } - } ); } diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs new file mode 100644 index 0000000..b2e9be9 --- /dev/null +++ b/lyra-ecs/src/system/mod.rs @@ -0,0 +1,156 @@ +use std::{ptr::NonNull, marker::PhantomData}; + +use crate::{world::World, View, Query, Access}; + +/// A system that does not mutate the world +pub trait System { + fn world_access(&self) -> Access; + fn execute(&mut self, world: NonNull) -> anyhow::Result<()>; +} + +/// A trait for converting something into a system. +pub trait IntoSystem { + type System: System; + + fn into_system(self) -> Self::System; +} + +trait FnArg { + type Arg<'a>; + + fn new() -> Self; + + unsafe fn get<'a>(&mut self, world: &'a World) -> Self::Arg<'a>; +} + +pub struct FnSystem { + inner: F,//for<'a> fn(Args) -> anyhow::Result<()> + //args: Args, + _p: PhantomData, +} + +/* impl<'a, F, Q> System for FnSystem +where + Q: FnArg, + F: Fn(Q::Arg<'a>,) -> anyhow::Result<()>, */ + + +impl System for FnSystem +where + A: FnArg, + F: for<'a> FnMut(A::Arg<'a>) -> anyhow::Result<()>, +{ + fn world_access(&self) -> Access { + todo!() + } + + fn execute(&mut self, world: NonNull) -> anyhow::Result<()> { + let world = unsafe { world.as_ref() }; + + let mut a = A::new(); + + let a = unsafe { a.get(world) }; + + (self.inner)(a)?; + + Ok(()) + } +} + +/* impl IntoSystem for F +where + A: FnArg, + F: for<'a> Fn(A::Arg<'a>) -> anyhow::Result<()>, +{ + type System = FnSystem; + + fn into_system(self) -> Self::System { + FnSystem { + _p: PhantomData, + inner: self + } + } +} */ + +/* impl IntoSystem for F +where + Q: Query, + F: for<'a> Fn( as FnArg>::Arg<'a>) -> anyhow::Result<()>, +{ + type System = FnSystem>; + + fn into_system(self) -> Self::System { + FnSystem { + _p: PhantomData, + inner: self + } + } +} */ + +impl IntoSystem for F +where + Q: Query, + F: for<'a> FnMut( as FnArg>::Arg<'a>) -> anyhow::Result<()>, +{ + type System = FnSystem>; + + fn into_system(self) -> Self::System { + FnSystem { + _p: PhantomData, + inner: self + } + } +} + +pub struct FnArgStorage { + query: Q, +} + +impl FnArg for FnArgStorage { + type Arg<'b> = View<'b, Q>; + + fn new() -> Self { + FnArgStorage { + query: Q::new(), + } + } + + unsafe fn get<'b>(&mut self, world: &'b World) -> Self::Arg<'b> { + let arch = world.archetypes.values().collect(); + let v = View::new(world, self.query, arch); + + v + } +} + +#[cfg(test)] +mod tests { + use std::{ptr::NonNull, sync::atomic::{AtomicU8, Ordering}, rc::Rc, ops::Add}; + + use crate::{tests::Vec2, View, QueryBorrow, world::World}; + use super::{System, IntoSystem}; + + #[test] + fn simple_system() { + let mut world = World::new(); + world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),)); + world.spawn((Vec2::rand(),)); + + let mut count = 0; + + let test_system = |view: View>| -> anyhow::Result<()> { + for v in view.into_iter() { + println!("Got v at: {:?}", v); + count += 1; + } + + Ok(()) + }; + + test_system.into_system().execute(NonNull::from(&world)).unwrap(); + + assert_eq!(count, 4); + } +} \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 1cd1109..ad8547e 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View, DefaultQuery}, resource::ResourceData}; +use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View}, resource::ResourceData, Query, AsQuery}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EntityId(pub u64); @@ -115,9 +115,9 @@ impl World { } } - pub fn view<'a, T: 'static + Component + DefaultQuery>(&'a self) -> ViewIter<'a, T::Query> { + pub fn view<'a, T: 'static + Component + AsQuery>(&'a self) -> ViewIter<'a, T::Query> { let archetypes = self.archetypes.values().collect(); - let v = View::new(self, T::default_query(), archetypes); + let v = View::new(self, T::Query::new(), archetypes); v.into_iter() }