From 9649267de93f45077fcfe2a7ac969b012ac3e4e0 Mon Sep 17 00:00:00 2001 From: Edvin Nillsson Date: Sun, 19 Mar 2023 19:51:30 +0100 Subject: [PATCH 01/12] Add query filters --- crates/ecs/src/filter.rs | 340 +++++++++++++++++++++++++++++++++++++++ crates/ecs/src/lib.rs | 1 + 2 files changed, 341 insertions(+) create mode 100644 crates/ecs/src/filter.rs diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs new file mode 100644 index 0000000..20b3c18 --- /dev/null +++ b/crates/ecs/src/filter.rs @@ -0,0 +1,340 @@ +//! Query filters can be used as [`SystemParameter`]s to narrow down system queries. + +use crate::{ComponentAccessDescriptor, Entity, ReadComponentVec, SystemParameter, World}; +use std::marker::PhantomData; + +/// A query filter that matches any entity with a given component type. +/// +/// # Example +/// ``` +/// # use ecs::filter::With; +/// # use ecs::Read; +/// # #[derive(Debug)] +/// # struct Position; +/// # struct Player; +/// fn player_position(position: Read, _: With) { +/// println!("A player is at position {:?}.", position); +/// } +/// ``` +#[derive(Debug)] +pub struct With { + phantom: PhantomData, +} + +impl SystemParameter for With { + type BorrowedData<'components> = ReadComponentVec<'components, Component>; + + fn borrow(world: &World) -> Self::BorrowedData<'_> { + world.borrow_component_vec::() + } + + unsafe fn fetch_parameter( + borrowed: &mut Self::BorrowedData<'_>, + entity: Entity, + ) -> Option { + if let Some(component_vec) = borrowed { + if let Some(Some(_)) = component_vec.get(entity.id) { + return Some(Self { + phantom: Default::default(), + }); + } + } + None + } + + fn component_access() -> ComponentAccessDescriptor { + todo!() + } +} + +/// A query filter that matches any entity without a given component type. +/// +/// # Example +/// ``` +/// # use ecs::filter::{With, Without}; +/// # use ecs::Read; +/// # #[derive(Debug)] +/// # struct Position; +/// # struct Player; +/// fn non_player_position(position: Read, _: Without) { +/// println!("A non-player entity is at position {:?}.", position); +/// } +/// ``` +#[derive(Debug)] +pub struct Without { + phantom: PhantomData, +} + +impl SystemParameter for Without { + type BorrowedData<'components> = ReadComponentVec<'components, Component>; + + fn borrow(world: &World) -> Self::BorrowedData<'_> { + world.borrow_component_vec::() + } + + unsafe fn fetch_parameter( + borrowed: &mut Self::BorrowedData<'_>, + entity: Entity, + ) -> Option { + if let Some(component_vec) = borrowed { + if let Some(Some(_)) = component_vec.get(entity.id) { + return None; + } + } + Some(Self { + phantom: Default::default(), + }) + } + + fn component_access() -> ComponentAccessDescriptor { + todo!() + } +} + +macro_rules! binary_filter_operation { + ($name:ident, $op:tt, $op_name:literal, $op_name_lowercase:literal) => { + /// A query filter that combines two filters using the ` + #[doc = $op_name] + ///` operation. + /// + #[doc = concat!( + "# Example\n```\n", + "# use {ecs::filters::With, ecs::filters::", stringify!($name), "};\n", + "# use ecs::Read;\n", + "# #[derive(Debug)]\n", + "# struct Position;\n", + "# struct Player;\n", + "# struct Enemy;\n", + "fn player_", $op_name_lowercase, "_enemy(position: Read, _: ", stringify!($name), ", With>) {\n", + " println!(\"An entity at position {:?} is a player ", $op_name_lowercase ," an enemy.\", position);\n", + "}\n```", + )] + #[derive(Debug)] + pub struct $name { + left: PhantomData, + right: PhantomData, + } + + impl SystemParameter for $name { + type BorrowedData<'components> = ( + ::BorrowedData<'components>, + ::BorrowedData<'components>, + ); + + fn borrow(world: &World) -> Self::BorrowedData<'_> { + ( + ::borrow(world), + ::borrow(world), + ) + } + + unsafe fn fetch_parameter( + borrowed: &mut Self::BorrowedData<'_>, + entity: Entity, + ) -> Option { + let (left_borrow, right_borrow) = borrowed; + let left = ::fetch_parameter(left_borrow, entity); + let right = ::fetch_parameter(right_borrow, entity); + (left.is_some() $op right.is_some()).then(|| Self { + left: PhantomData::default(), + right: PhantomData::default(), + }) + } + + fn component_access() -> ComponentAccessDescriptor { + todo!() + } + } + } +} + +binary_filter_operation!(And, &&, "And", "and"); +binary_filter_operation!(Or, ||, "Or", "or"); +binary_filter_operation!(Xor, ^, "Xor", "xor"); + +/// A query filter that inverts another filter. +/// +/// # Example +/// ``` +/// # use ecs::filter::{Not, With}; +/// # use ecs::Read; +/// # #[derive(Debug)] +/// # struct Position; +/// # struct Player; +/// fn non_player_position(position: Read, _: Not>) { +/// println!("A non-player entity is at position {:?}.", position); +/// } +/// ``` +#[derive(Debug)] +pub struct Not { + phantom: PhantomData, +} + +impl SystemParameter for Not { + type BorrowedData<'components> = ::BorrowedData<'components>; + + fn borrow(world: &World) -> Self::BorrowedData<'_> { + ::borrow(world) + } + + unsafe fn fetch_parameter( + borrowed: &mut Self::BorrowedData<'_>, + entity: Entity, + ) -> Option { + if ::fetch_parameter(borrowed, entity).is_some() { + None + } else { + Some(Self { + phantom: PhantomData::default(), + }) + } + } + + fn component_access() -> ComponentAccessDescriptor { + todo!() + } +} + +/// A query filter that matches any entity. +/// +/// # Example +/// ``` +/// # use ecs::filter::Any; +/// fn all_entities(_: Any) { +/// println!("Hello from an entity!"); +/// } +/// ``` +#[derive(Debug)] +pub struct Any {} + +impl SystemParameter for Any { + type BorrowedData<'components> = (); + + fn borrow(_world: &World) -> Self::BorrowedData<'_> {} + + unsafe fn fetch_parameter( + _borrowed: &mut Self::BorrowedData<'_>, + _entity: Entity, + ) -> Option { + Some(Self {}) + } + + fn component_access() -> ComponentAccessDescriptor { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Read, Write}; + use proptest::proptest; + use test_log::test; + + #[derive(Debug)] + struct A; + + #[derive(Debug)] + struct B; + + #[derive(Debug)] + struct C; + + fn test_filter((a, b, c): (bool, bool, bool)) -> Option { + let mut world: World = Default::default(); + + let entity: Entity = Default::default(); + world.entities.push(entity); + + if a { + world.create_component_vec_and_add(entity, A); + } + if b { + world.create_component_vec_and_add(entity, B); + } + if c { + world.create_component_vec_and_add(entity, C); + } + + let mut borrowed = ::borrow(&world); + unsafe { ::fetch_parameter(&mut borrowed, entity) } + } + + macro_rules! identity { + ($vars:expr, $expr:ty, $expected:literal) => { + assert_eq!(test_filter::<$expr>($vars).is_some(), $expected); + }; + ($vars:expr, $lhs:ty, $rhs:ty) => { + assert_eq!( + test_filter::<$lhs>($vars).is_some(), + test_filter::<$rhs>($vars).is_some(), + ); + }; + } + + proptest! { + #[test] + fn test_filter_identities(vars: (bool, bool, bool)) { + identity!(vars, With, Read); + identity!(vars, With, Write); + + identity!(vars, Any, true); + identity!(vars, Not, false); + + identity!(vars, Without, Not>); + identity!(vars, With, Not>); + + // Involution + identity!(vars, Not>>, With); + + // Dominance + identity!(vars, Or, Any>, true); + identity!(vars, And, Not>, false); + + // Identity + identity!(vars, Or, Not>, With); + identity!(vars, And, Any>, With); + identity!(vars, Xor, Not>, With); + + // Complementarity + identity!(vars, Or, Without>, true); + identity!(vars, And, Without>, false); + identity!(vars, Xor, Without>, true); + + // Idempotence + identity!(vars, Or, With>, With); + identity!(vars, And, With>, With); + identity!(vars, Xor, With>, false); + + // Complementarity + identity!(vars, Or, Without>, true); + identity!(vars, And, Without>, false); + identity!(vars, Xor, Without>, true); + + // Commutativity + identity!(vars, Or, With>, Or, With>); + identity!(vars, And, With>, And, With>); + identity!(vars, Xor, With>, Xor, With>); + + // Associativity + identity!(vars, Or, With>, With>, Or, Or, With>>); + identity!(vars, And, With>, With>, And, And, With>>); + identity!(vars, Xor, With>, With>, Xor, Xor, With>>); + + // Distributivity + identity!(vars, Or, And, With>>, And, With>, Or, With>>); + identity!(vars, And, Or, With>>, Or, With>, And, With>>); + + // Absorption + identity!(vars, And, Or, With>>, With); + + // De Morgan's laws + identity!(vars, Or, With>, Not, Without>>); + identity!(vars, And, With>, Not, Without>>); + + // Xor expressed in terms of And, Or and Not + identity!(vars, Xor, With>, Or, Without>, And, With>>); + identity!(vars, Xor, With>, And, With>, Or, Without>>); + } + } +} diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index b6fd4ca..7009160 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -27,6 +27,7 @@ clippy::large_enum_variant )] +pub mod filter; pub mod logging; use crossbeam::channel::{Receiver, TryRecvError}; From b1a61abdd8d5d89f20a8cec54573e3ac7fdabc5b Mon Sep 17 00:00:00 2001 From: Edvin Nilsson Date: Mon, 20 Mar 2023 14:16:31 +0100 Subject: [PATCH 02/12] Implement component_accesses for filters --- crates/ecs/src/filter.rs | 25 +++++++++++++++---------- crates/ecs/src/lib.rs | 13 +++++++------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index 20b3c18..f6cf4e3 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -1,6 +1,7 @@ //! Query filters can be used as [`SystemParameter`]s to narrow down system queries. use crate::{ComponentAccessDescriptor, Entity, ReadComponentVec, SystemParameter, World}; +use std::any::TypeId; use std::marker::PhantomData; /// A query filter that matches any entity with a given component type. @@ -42,8 +43,8 @@ impl SystemParameter for With ComponentAccessDescriptor { - todo!() + fn component_accesses() -> Vec { + vec![ComponentAccessDescriptor::Read(TypeId::of::())] } } @@ -86,8 +87,8 @@ impl SystemParameter for Without ComponentAccessDescriptor { - todo!() + fn component_accesses() -> Vec { + vec![ComponentAccessDescriptor::Read(TypeId::of::())] } } @@ -141,8 +142,12 @@ macro_rules! binary_filter_operation { }) } - fn component_access() -> ComponentAccessDescriptor { - todo!() + fn component_accesses() -> Vec { + [ + ::component_accesses(), + ::component_accesses(), + ] + .concat() } } } @@ -190,8 +195,8 @@ impl SystemParameter for Not { } } - fn component_access() -> ComponentAccessDescriptor { - todo!() + fn component_accesses() -> Vec { + ::component_accesses() } } @@ -219,8 +224,8 @@ impl SystemParameter for Any { Some(Self {}) } - fn component_access() -> ComponentAccessDescriptor { - todo!() + fn component_accesses() -> Vec { + vec![] } } diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index 7009160..43de7a3 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -374,7 +374,8 @@ pub trait SystemParameter: Send + Sync + Sized { ) -> Option; /// A description of what data is accessed and how (read/write). - fn component_access() -> ComponentAccessDescriptor; + /// TODO: component_vec should not be locked for filters + fn component_accesses() -> Vec; } trait SystemParameterFunction: 'static { @@ -419,8 +420,8 @@ impl SystemParameter for Read<'_, Comp None } - fn component_access() -> ComponentAccessDescriptor { - ComponentAccessDescriptor::Read(TypeId::of::()) + fn component_accesses() -> Vec { + vec![ComponentAccessDescriptor::Read(TypeId::of::())] } } @@ -448,8 +449,8 @@ impl SystemParameter for Write<'_, Com None } - fn component_access() -> ComponentAccessDescriptor { - ComponentAccessDescriptor::Write(TypeId::of::()) + fn component_accesses() -> Vec { + vec![ComponentAccessDescriptor::Write(TypeId::of::())] } } @@ -493,7 +494,7 @@ macro_rules! impl_system_parameter_function { paste! { impl<$([]: SystemParameter,)*> SystemParameters for ($([],)*) { fn component_accesses() -> Vec { - vec![$([]::component_access(),)*] + vec![$([]::component_accesses(),)*].into_iter().flatten().collect() } } From b189ebc8dc2b2174b6449a75d4d86e2e8abefe1c Mon Sep 17 00:00:00 2001 From: Edvin Nilsson Date: Mon, 20 Mar 2023 14:50:03 +0100 Subject: [PATCH 03/12] Fix incorrect module name in macro-generated doctest --- crates/ecs/src/filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index f6cf4e3..613c690 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -100,7 +100,7 @@ macro_rules! binary_filter_operation { /// #[doc = concat!( "# Example\n```\n", - "# use {ecs::filters::With, ecs::filters::", stringify!($name), "};\n", + "# use {ecs::filter::With, ecs::filter::", stringify!($name), "};\n", "# use ecs::Read;\n", "# #[derive(Debug)]\n", "# struct Position;\n", From e1ae204db983b595507e991d4115a6c19cad57cc Mon Sep 17 00:00:00 2001 From: Edvin Nilsson <42613683+EdvinNilsson@users.noreply.github.com> Date: Tue, 21 Mar 2023 20:44:12 +0100 Subject: [PATCH 04/12] Replace Default::default() Co-authored-by: Martin --- crates/ecs/src/filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index 613c690..c446d2e 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -246,9 +246,9 @@ mod tests { struct C; fn test_filter((a, b, c): (bool, bool, bool)) -> Option { - let mut world: World = Default::default(); + let mut world = World::default(); - let entity: Entity = Default::default(); + let entity = Entity::default(); world.entities.push(entity); if a { From 469b84a01044a10771f6cfca67e47c580ca5c397 Mon Sep 17 00:00:00 2001 From: Edvin Nilsson Date: Tue, 21 Mar 2023 20:58:18 +0100 Subject: [PATCH 05/12] Separate the tests, add transitivity test and use proptest annotation --- crates/ecs/src/filter.rs | 233 ++++++++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 74 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index c446d2e..4f96451 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -100,7 +100,7 @@ macro_rules! binary_filter_operation { /// #[doc = concat!( "# Example\n```\n", - "# use {ecs::filter::With, ecs::filter::", stringify!($name), "};\n", + "# use ecs::filter::{With, ", stringify!($name), "};\n", "# use ecs::Read;\n", "# #[derive(Debug)]\n", "# struct Position;\n", @@ -232,9 +232,9 @@ impl SystemParameter for Any { #[cfg(test)] mod tests { use super::*; - use crate::{Read, Write}; - use proptest::proptest; + use crate::Read; use test_log::test; + use test_strategy::proptest; #[derive(Debug)] struct A; @@ -245,7 +245,7 @@ mod tests { #[derive(Debug)] struct C; - fn test_filter((a, b, c): (bool, bool, bool)) -> Option { + fn test_filter(a: bool, b: bool, c: bool) -> Option { let mut world = World::default(); let entity = Entity::default(); @@ -265,81 +265,166 @@ mod tests { unsafe { ::fetch_parameter(&mut borrowed, entity) } } - macro_rules! identity { - ($vars:expr, $expr:ty, $expected:literal) => { - assert_eq!(test_filter::<$expr>($vars).is_some(), $expected); + macro_rules! logically_eq { + // Test with zero variables + ($expr:ty, $expected:literal) => { + assert_eq!( + test_filter::<$expr>(false, false, false).is_some(), + $expected + ); + }; + // Test with three variables a, b and c + (($a:expr, $b:expr, $c:expr), $lhs:ty, $rhs:ty) => { + assert_eq!( + test_filter::<$lhs>($a, $b, $c).is_some(), + test_filter::<$rhs>($a, $b, $c).is_some(), + ); }; - ($vars:expr, $lhs:ty, $rhs:ty) => { + // Test with two variables a and b + (($a:expr, $b:expr), $lhs:ty, $rhs:ty) => { assert_eq!( - test_filter::<$lhs>($vars).is_some(), - test_filter::<$rhs>($vars).is_some(), + test_filter::<$lhs>($a, $b, false).is_some(), + test_filter::<$rhs>($a, $b, false).is_some(), + ); + }; + // Test with one variable a + ($a:expr, $lhs:ty, $rhs:ty) => { + assert_eq!( + test_filter::<$lhs>($a, false, false).is_some(), + test_filter::<$rhs>($a, false, false).is_some(), ); }; } - proptest! { - #[test] - fn test_filter_identities(vars: (bool, bool, bool)) { - identity!(vars, With, Read); - identity!(vars, With, Write); - - identity!(vars, Any, true); - identity!(vars, Not, false); - - identity!(vars, Without, Not>); - identity!(vars, With, Not>); - - // Involution - identity!(vars, Not>>, With); - - // Dominance - identity!(vars, Or, Any>, true); - identity!(vars, And, Not>, false); - - // Identity - identity!(vars, Or, Not>, With); - identity!(vars, And, Any>, With); - identity!(vars, Xor, Not>, With); - - // Complementarity - identity!(vars, Or, Without>, true); - identity!(vars, And, Without>, false); - identity!(vars, Xor, Without>, true); - - // Idempotence - identity!(vars, Or, With>, With); - identity!(vars, And, With>, With); - identity!(vars, Xor, With>, false); - - // Complementarity - identity!(vars, Or, Without>, true); - identity!(vars, And, Without>, false); - identity!(vars, Xor, Without>, true); - - // Commutativity - identity!(vars, Or, With>, Or, With>); - identity!(vars, And, With>, And, With>); - identity!(vars, Xor, With>, Xor, With>); - - // Associativity - identity!(vars, Or, With>, With>, Or, Or, With>>); - identity!(vars, And, With>, With>, And, And, With>>); - identity!(vars, Xor, With>, With>, Xor, Xor, With>>); - - // Distributivity - identity!(vars, Or, And, With>>, And, With>, Or, With>>); - identity!(vars, And, Or, With>>, Or, With>, And, With>>); - - // Absorption - identity!(vars, And, Or, With>>, With); - - // De Morgan's laws - identity!(vars, Or, With>, Not, Without>>); - identity!(vars, And, With>, Not, Without>>); - - // Xor expressed in terms of And, Or and Not - identity!(vars, Xor, With>, Or, Without>, And, With>>); - identity!(vars, Xor, With>, And, With>, Or, Without>>); - } + #[proptest] + fn basic_test(a: bool) { + logically_eq!(a, With, Read); + + logically_eq!(Any, true); + logically_eq!(Not, false); + + logically_eq!(a, Without, Not>); + logically_eq!(a, With, Not>); + } + + #[proptest] + fn test_involution(a: bool) { + logically_eq!(a, Not>>, With); + } + + #[proptest] + fn test_dominance(a: bool) { + logically_eq!(a, Or, Any>, Any); + logically_eq!(a, And, Not>, Not); + } + + #[proptest] + fn test_identity_elem(a: bool) { + logically_eq!(a, Or, Not>, With); + logically_eq!(a, And, Any>, With); + logically_eq!(a, Xor, Not>, With); + } + + #[proptest] + fn test_complementarity(a: bool) { + logically_eq!(a, Or, Without>, Any); + logically_eq!(a, And, Without>, Not); + logically_eq!(a, Xor, Without>, Any); + } + + #[proptest] + fn test_idempotence(a: bool) { + logically_eq!(a, Or, With>, With); + logically_eq!(a, And, With>, With); + logically_eq!(a, Xor, With>, Not); + } + + #[proptest] + fn test_commutativity(a: bool, b: bool) { + logically_eq!((a, b), Or, With>, Or, With>); + logically_eq!((a, b), And, With>, And, With>); + logically_eq!((a, b), Xor, With>, Xor, With>); + } + + #[proptest] + fn test_associativity(a: bool, b: bool) { + logically_eq!( + (a, b), + Or, With>, With>, + Or, Or, With>> + ); + logically_eq!( + (a, b), + And, With>, With>, + And, And, With>> + ); + logically_eq!( + (a, b), + Xor, With>, With>, + Xor, Xor, With>> + ); + } + + #[proptest] + fn test_distributivity(a: bool, b: bool, c: bool) { + logically_eq!( + (a, b, c), + Or, And, With>>, + And, With>, Or, With>> + ); + logically_eq!( + (a, b, c), + And, Or, With>>, + Or, With>, And, With>> + ); + } + + #[proptest] + fn test_absorption(a: bool) { + logically_eq!(a, And, Or, With>>, With); + } + + #[proptest] + fn test_de_morgans_laws(a: bool, b: bool) { + logically_eq!( + (a, b), + Or, With>, + Not, Without>> + ); + logically_eq!( + (a, b), + And, With>, + Not, Without>> + ); + } + + #[proptest] + fn test_xor_with_and_or_and_not(a: bool, b: bool) { + logically_eq!( + (a, b), + Xor, With>, + Or, Without>, And, With>> + ); + logically_eq!( + (a, b), + Xor, With>, + And, With>, Or, Without>> + ); + } + + macro_rules! implies { + ($p:ty, $q:ty) => { Or, $q> } + } + + #[proptest] + fn test_transitivity(a: bool, b: bool, c: bool) { + logically_eq!( + (a, b, c), + implies!( + And, With>, And, With>>, + And, With> + ), + Any + ); } } From aa61e9deaaa97a17dfdd6851462dfe439492057e Mon Sep 17 00:00:00 2001 From: Edvin Nilsson Date: Wed, 22 Mar 2023 11:01:24 +0100 Subject: [PATCH 06/12] Make SystemParameter pub(crate), add Filter trait, move Any to tests --- crates/ecs/src/filter.rs | 77 ++++++++++++++++++++-------------------- crates/ecs/src/lib.rs | 2 +- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index 4f96451..067a388 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -1,9 +1,12 @@ -//! Query filters can be used as [`SystemParameter`]s to narrow down system queries. +//! Query filters can be used as system parameters to narrow down system queries. use crate::{ComponentAccessDescriptor, Entity, ReadComponentVec, SystemParameter, World}; use std::any::TypeId; use std::marker::PhantomData; +/// A query filter +pub trait Filter {} + /// A query filter that matches any entity with a given component type. /// /// # Example @@ -22,6 +25,7 @@ pub struct With { phantom: PhantomData, } +impl Filter for With {} impl SystemParameter for With { type BorrowedData<'components> = ReadComponentVec<'components, Component>; @@ -66,6 +70,7 @@ pub struct Without { phantom: PhantomData, } +impl Filter for Without {} impl SystemParameter for Without { type BorrowedData<'components> = ReadComponentVec<'components, Component>; @@ -111,12 +116,13 @@ macro_rules! binary_filter_operation { "}\n```", )] #[derive(Debug)] - pub struct $name { + pub struct $name { left: PhantomData, right: PhantomData, } - impl SystemParameter for $name { + impl Filter for $name {} + impl SystemParameter for $name { type BorrowedData<'components> = ( ::BorrowedData<'components>, ::BorrowedData<'components>, @@ -171,11 +177,12 @@ binary_filter_operation!(Xor, ^, "Xor", "xor"); /// } /// ``` #[derive(Debug)] -pub struct Not { +pub struct Not { phantom: PhantomData, } -impl SystemParameter for Not { +impl Filter for Not {} +impl SystemParameter for Not { type BorrowedData<'components> = ::BorrowedData<'components>; fn borrow(world: &World) -> Self::BorrowedData<'_> { @@ -200,35 +207,6 @@ impl SystemParameter for Not { } } -/// A query filter that matches any entity. -/// -/// # Example -/// ``` -/// # use ecs::filter::Any; -/// fn all_entities(_: Any) { -/// println!("Hello from an entity!"); -/// } -/// ``` -#[derive(Debug)] -pub struct Any {} - -impl SystemParameter for Any { - type BorrowedData<'components> = (); - - fn borrow(_world: &World) -> Self::BorrowedData<'_> {} - - unsafe fn fetch_parameter( - _borrowed: &mut Self::BorrowedData<'_>, - _entity: Entity, - ) -> Option { - Some(Self {}) - } - - fn component_accesses() -> Vec { - vec![] - } -} - #[cfg(test)] mod tests { use super::*; @@ -236,6 +214,29 @@ mod tests { use test_log::test; use test_strategy::proptest; + /// A query filter that matches any entity. + /// Only used for testing. + #[derive(Debug)] + pub struct Any {} + + impl Filter for Any {} + impl SystemParameter for Any { + type BorrowedData<'components> = (); + + fn borrow(_world: &World) -> Self::BorrowedData<'_> {} + + unsafe fn fetch_parameter( + _borrowed: &mut Self::BorrowedData<'_>, + _entity: Entity, + ) -> Option { + Some(Self {}) + } + + fn component_accesses() -> Vec { + vec![] + } + } + #[derive(Debug)] struct A; @@ -245,7 +246,7 @@ mod tests { #[derive(Debug)] struct C; - fn test_filter(a: bool, b: bool, c: bool) -> Option { + fn test_filter(a: bool, b: bool, c: bool) -> Option

{ let mut world = World::default(); let entity = Entity::default(); @@ -261,12 +262,12 @@ mod tests { world.create_component_vec_and_add(entity, C); } - let mut borrowed = ::borrow(&world); - unsafe { ::fetch_parameter(&mut borrowed, entity) } + let mut borrowed =

::borrow(&world); + unsafe {

::fetch_parameter(&mut borrowed, entity) } } macro_rules! logically_eq { - // Test with zero variables + // Test with zero variables and compare with boolean ($expr:ty, $expected:literal) => { assert_eq!( test_filter::<$expr>(false, false, false).is_some(), diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index 43de7a3..e825f45 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -358,7 +358,7 @@ pub trait SystemParameters: Send + Sync { } /// Something that can be passed to a `ecs::System`. -pub trait SystemParameter: Send + Sync + Sized { +pub(crate) trait SystemParameter: Send + Sync + Sized { /// Contains a borrow of components from `ecs::World`. type BorrowedData<'components>; From 68ac2bf00e9dd93bd05d806fba940202d209dce4 Mon Sep 17 00:00:00 2001 From: Edvin Nilsson Date: Wed, 22 Mar 2023 11:07:16 +0100 Subject: [PATCH 07/12] Add safety comment --- crates/ecs/src/filter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index 067a388..ca3ecb8 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -263,6 +263,7 @@ mod tests { } let mut borrowed =

::borrow(&world); + // SAFETY: This is safe because the result from fetch_parameter will not outlive borrowed unsafe {

::fetch_parameter(&mut borrowed, entity) } } From dd735fa78c8485a35cdef7cfae8b6fcec64c87d7 Mon Sep 17 00:00:00 2001 From: Edvin Nillsson Date: Wed, 29 Mar 2023 20:15:03 +0200 Subject: [PATCH 08/12] Filters that work with archetypes --- crates/ecs/src/filter.rs | 278 +++++++++++++++++++++++---------------- crates/ecs/src/lib.rs | 274 +++++++++++++++++++++++--------------- 2 files changed, 330 insertions(+), 222 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index eea2042..d5cc742 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -1,6 +1,11 @@ //! Query filters can be used as system parameters to narrow down system queries. -use crate::{ComponentAccessDescriptor, Entity, ReadComponentVec, SystemParameter, World}; +use crate::{ + ArchetypeIndex, ComponentAccessDescriptor, SystemParameter, SystemParameterResult, World, +}; +use std::any::TypeId; +use std::collections::HashSet; +use std::fmt::Debug; use std::marker::PhantomData; /// A query filter @@ -24,30 +29,41 @@ pub struct With { phantom: PhantomData, } -impl Filter for With {} -impl SystemParameter for With { - type BorrowedData<'components> = ReadComponentVec<'components, Component>; +impl Filter for With {} +impl SystemParameter for With { + type BorrowedData<'components> = (); - fn borrow(world: &World) -> Self::BorrowedData<'_> { - world.borrow_component_vec::() + fn borrow<'world>( + _: &'world World, + _: &[ArchetypeIndex], + ) -> SystemParameterResult> { + Ok(()) } - unsafe fn fetch_parameter( - borrowed: &mut Self::BorrowedData<'_>, - entity: Entity, - ) -> Option { - if let Some(component_vec) = borrowed { - if let Some(Some(_)) = component_vec.get(entity.id) { - return Some(Self { - phantom: Default::default(), - }); - } - } + unsafe fn fetch_parameter(_: &mut Self::BorrowedData<'_>) -> Option> { + Some(Some(Self { + phantom: PhantomData::default(), + })) + } + + fn component_access() -> Option { None } - fn component_accesses() -> Vec { - vec![ComponentAccessDescriptor::read::()] + fn iterates_over_entities() -> bool { + false + } + + fn base_signature() -> Option { + Some(TypeId::of::()) + } + + fn filter(_universe: &HashSet, world: &World) -> HashSet { + world + .component_typeid_to_archetype_indices + .get(&TypeId::of::()) + .cloned() + .unwrap_or_default() } } @@ -59,42 +75,13 @@ impl SystemParameter for With, _: Without) { /// println!("A non-player entity is at position {:?}.", position); /// } /// ``` -#[derive(Debug)] -pub struct Without { - phantom: PhantomData, -} - -impl Filter for Without {} -impl SystemParameter for Without { - type BorrowedData<'components> = ReadComponentVec<'components, Component>; - - fn borrow(world: &World) -> Self::BorrowedData<'_> { - world.borrow_component_vec::() - } - - unsafe fn fetch_parameter( - borrowed: &mut Self::BorrowedData<'_>, - entity: Entity, - ) -> Option { - if let Some(component_vec) = borrowed { - if let Some(Some(_)) = component_vec.get(entity.id) { - return None; - } - } - Some(Self { - phantom: Default::default(), - }) - } - - fn component_accesses() -> Vec { - vec![ComponentAccessDescriptor::read::()] - } -} +pub type Without = Not>; macro_rules! binary_filter_operation { ($name:ident, $op:tt, $op_name:literal, $op_name_lowercase:literal) => { @@ -108,7 +95,9 @@ macro_rules! binary_filter_operation { "# use ecs::Read;\n", "# #[derive(Debug)]\n", "# struct Position;\n", + "# #[derive(Debug)]\n", "# struct Player;\n", + "# #[derive(Debug)]\n", "# struct Enemy;\n", "fn player_", $op_name_lowercase, "_enemy(position: Read, _: ", stringify!($name), ", With>) {\n", " println!(\"An entity at position {:?} is a player ", $op_name_lowercase ," an enemy.\", position);\n", @@ -122,44 +111,47 @@ macro_rules! binary_filter_operation { impl Filter for $name {} impl SystemParameter for $name { - type BorrowedData<'components> = ( - ::BorrowedData<'components>, - ::BorrowedData<'components>, - ); + type BorrowedData<'components> = (); - fn borrow(world: &World) -> Self::BorrowedData<'_> { - ( - ::borrow(world), - ::borrow(world), - ) + fn borrow<'world>( + _: &'world World, + _: &[ArchetypeIndex], + ) -> SystemParameterResult> { + Ok(()) } - unsafe fn fetch_parameter( - borrowed: &mut Self::BorrowedData<'_>, - entity: Entity, - ) -> Option { - let (left_borrow, right_borrow) = borrowed; - let left = ::fetch_parameter(left_borrow, entity); - let right = ::fetch_parameter(right_borrow, entity); - (left.is_some() $op right.is_some()).then(|| Self { + unsafe fn fetch_parameter(_: &mut Self::BorrowedData<'_>) -> Option> { + Some(Some(Self { left: PhantomData::default(), right: PhantomData::default(), - }) + })) + } + + fn component_access() -> Option { + None + } + + fn iterates_over_entities() -> bool { + false + } + + fn base_signature() -> Option { + None } - fn component_accesses() -> Vec { - [ - ::component_accesses(), - ::component_accesses(), - ] - .concat() + fn filter( + universe: &HashSet, + world: &World, + ) -> HashSet { + &::filter(universe, world) + $op &::filter(universe, world) } } } } -binary_filter_operation!(And, &&, "And", "and"); -binary_filter_operation!(Or, ||, "Or", "or"); +binary_filter_operation!(And, &, "And", "and"); +binary_filter_operation!(Or, |, "Or", "or"); binary_filter_operation!(Xor, ^, "Xor", "xor"); /// A query filter that inverts another filter. @@ -170,6 +162,7 @@ binary_filter_operation!(Xor, ^, "Xor", "xor"); /// # use ecs::Read; /// # #[derive(Debug)] /// # struct Position; +/// #[derive(Debug)] /// # struct Player; /// fn non_player_position(position: Read, _: Not>) { /// println!("A non-player entity is at position {:?}.", position); @@ -182,34 +175,46 @@ pub struct Not { impl Filter for Not {} impl SystemParameter for Not { - type BorrowedData<'components> = ::BorrowedData<'components>; + type BorrowedData<'components> = (); - fn borrow(world: &World) -> Self::BorrowedData<'_> { - ::borrow(world) + fn borrow<'world>( + _: &'world World, + _: &[ArchetypeIndex], + ) -> SystemParameterResult> { + Ok(()) } - unsafe fn fetch_parameter( - borrowed: &mut Self::BorrowedData<'_>, - entity: Entity, - ) -> Option { - if ::fetch_parameter(borrowed, entity).is_some() { - None - } else { - Some(Self { - phantom: PhantomData::default(), - }) - } + unsafe fn fetch_parameter(_: &mut Self::BorrowedData<'_>) -> Option> { + Some(Some(Self { + phantom: PhantomData::default(), + })) + } + + fn component_access() -> Option { + None } - fn component_accesses() -> Vec { - ::component_accesses() + fn iterates_over_entities() -> bool { + false + } + + fn base_signature() -> Option { + None + } + + fn filter(universe: &HashSet, world: &World) -> HashSet { + universe + .difference(&::filter(universe, world)) + .cloned() + .collect() } } #[cfg(test)] mod tests { use super::*; - use crate::Read; + use crate::{Entity, IntoSystem, Read, System, Write}; + use color_eyre::Report; use test_log::test; use test_strategy::proptest; @@ -222,17 +227,31 @@ mod tests { impl SystemParameter for Any { type BorrowedData<'components> = (); - fn borrow(_world: &World) -> Self::BorrowedData<'_> {} + fn borrow<'world>( + _: &'world World, + _: &[ArchetypeIndex], + ) -> SystemParameterResult> { + Ok(()) + } + + unsafe fn fetch_parameter(_: &mut Self::BorrowedData<'_>) -> Option> { + Some(Some(Self {})) + } + + fn component_access() -> Option { + None + } + + fn iterates_over_entities() -> bool { + false + } - unsafe fn fetch_parameter( - _borrowed: &mut Self::BorrowedData<'_>, - _entity: Entity, - ) -> Option { - Some(Self {}) + fn base_signature() -> Option { + None } - fn component_accesses() -> Vec { - vec![] + fn filter(universe: &HashSet, _: &World) -> HashSet { + universe.clone() } } @@ -245,54 +264,85 @@ mod tests { #[derive(Debug)] struct C; - fn test_filter(a: bool, b: bool, c: bool) -> Option

{ + #[derive(Debug)] + struct TestResult(bool); + + fn test_filter( + a: bool, + b: bool, + c: bool, + ) -> Result { let mut world = World::default(); let entity = Entity::default(); world.entities.push(entity); + world.create_component_vec_and_add(entity, TestResult(false))?; + if a { - world.create_component_vec_and_add(entity, A); + world.create_component_vec_and_add(entity, A)?; } if b { - world.create_component_vec_and_add(entity, B); + world.create_component_vec_and_add(entity, B)?; } if c { - world.create_component_vec_and_add(entity, C); + world.create_component_vec_and_add(entity, C)?; } - let mut borrowed =

::borrow(&world); + let system = |mut test_result: Write, _: Filter| { + test_result.0 = true; + }; + + let function_system = system.into_system(); + function_system.run(&world)?; + + let archetypes: Vec = world + .get_archetype_indices(&[TypeId::of::()]) + .into_iter() + .collect(); + + let mut borrowed = + as SystemParameter>::borrow(&world, &archetypes).unwrap(); + // SAFETY: This is safe because the result from fetch_parameter will not outlive borrowed - unsafe {

::fetch_parameter(&mut borrowed, entity) } + unsafe { + if let Some(Some(result)) = + as SystemParameter>::fetch_parameter(&mut borrowed) + { + Ok(result.0) + } else { + panic!("Could not fetch the test result.") + } + } } macro_rules! logically_eq { // Test with zero variables and compare with boolean ($expr:ty, $expected:literal) => { assert_eq!( - test_filter::<$expr>(false, false, false).is_some(), + test_filter::<$expr>(false, false, false).unwrap(), $expected ); }; // Test with three variables a, b and c (($a:expr, $b:expr, $c:expr), $lhs:ty, $rhs:ty) => { assert_eq!( - test_filter::<$lhs>($a, $b, $c).is_some(), - test_filter::<$rhs>($a, $b, $c).is_some(), + test_filter::<$lhs>($a, $b, $c).unwrap(), + test_filter::<$rhs>($a, $b, $c).unwrap(), ); }; // Test with two variables a and b (($a:expr, $b:expr), $lhs:ty, $rhs:ty) => { assert_eq!( - test_filter::<$lhs>($a, $b, false).is_some(), - test_filter::<$rhs>($a, $b, false).is_some(), + test_filter::<$lhs>($a, $b, false).unwrap(), + test_filter::<$rhs>($a, $b, false).unwrap(), ); }; // Test with one variable a ($a:expr, $lhs:ty, $rhs:ty) => { assert_eq!( - test_filter::<$lhs>($a, false, false).is_some(), - test_filter::<$rhs>($a, false, false).is_some(), + test_filter::<$lhs>($a, false, false).unwrap(), + test_filter::<$rhs>($a, false, false).unwrap(), ); }; } diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index 72a7133..c23926c 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -36,7 +36,7 @@ use core::panic; use crossbeam::channel::{bounded, Receiver, Sender, TryRecvError}; use paste::paste; use std::any::{Any, TypeId}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; @@ -425,7 +425,7 @@ pub struct World { /// This will result in two vectors containing the indices of the `Archetype`s that store these types. /// By taking the intersection of these vectors you will know which `Archetype` contain both A and B. /// This could be the archetypes: (A,B), (A,B,C), (A,B,...) etc. - component_typeid_to_archetype_indices: HashMap>, + component_typeid_to_archetype_indices: HashMap>, /// todo(#72): Contains all `TypeId`s of the components that `World` stores. /// todo(#72): Remove, only used to showcase how archetypes can be used in querying. stored_types: Vec, @@ -456,8 +456,8 @@ impl World { let archetype_index = 0; self.component_typeid_to_archetype_indices .entry(component_typeid) - .or_insert(vec![]) - .push(archetype_index); + .or_default() + .insert(archetype_index); } // todo(#38) Some code is mistakingly part of Application instead of World @@ -489,8 +489,8 @@ impl World { let component_typeid = component_vec.stored_type(); self.component_typeid_to_archetype_indices .entry(component_typeid) - .or_insert(vec![]) - .push(archetype_index); + .or_default() + .insert(archetype_index); }); self.archetypes.push(archetype); @@ -534,52 +534,35 @@ impl World { Ok(()) } - fn borrow_component_vecs_with_signature( - &self, - signature: &[TypeId], - ) -> WorldResult>> { - let archetype_indices = self.get_archetype_indices(signature); - self.borrow_component_vecs(&archetype_indices) - } - - fn borrow_component_vecs_with_signature_mut( - &self, - signature: &[TypeId], - ) -> WorldResult>> { - let archetype_indices = self.get_archetype_indices(signature); - self.borrow_component_vecs_mut(&archetype_indices) - } - - /// Returns the indices of all archetypes that atleast contain the given signature. + /// Returns the indices of all archetypes that at least contain the given signature. /// /// An example: if there exists the archetypes: (A), (A,B), (B,C), (A,B,C) /// and the signature (A,B) is given, the indices for archetypes: (A,B) and /// (A,B,C) will be returned as they both contain (A,B), while (A) only /// contains A components and no B components and (B,C) only contain B and C /// components and no A components. - fn get_archetype_indices(&self, signature: &[TypeId]) -> Vec<&ArchetypeIndex> { - let all_archetypes_with_signature_types: WorldResult> = signature - .iter() - .map(|componet_typeid| { - self.component_typeid_to_archetype_indices - .get(componet_typeid) - .ok_or(WorldError::ComponentTypeDoesNotExist(*componet_typeid)) - }) - .collect(); + fn get_archetype_indices(&self, signature: &[TypeId]) -> HashSet { + let all_archetypes_with_signature_types: WorldResult>> = + signature + .iter() + .map(|component_typeid| { + self.component_typeid_to_archetype_indices + .get(component_typeid) + .cloned() + .ok_or(WorldError::ComponentTypeDoesNotExist(*component_typeid)) + }) + .collect(); match all_archetypes_with_signature_types { - Ok(archetype_indices) => return find_intersecting_signature_indices(archetype_indices), - Err(_) => vec![], + Ok(archetype_indices) => intersection_of_multiple_sets(&archetype_indices), + Err(_) => HashSet::new(), } } - fn get_archetypes( - &self, - archetype_indices: &[&ArchetypeIndex], - ) -> WorldResult> { + fn get_archetypes(&self, archetype_indices: &[ArchetypeIndex]) -> WorldResult> { let archetypes: Result, _> = archetype_indices .iter() - .map(|&&archetype_index| { + .map(|&archetype_index| { self.archetypes .get(archetype_index) .ok_or(WorldError::ArchetypeDoesNotExist(archetype_index)) @@ -590,7 +573,7 @@ impl World { fn borrow_component_vecs( &self, - archetype_indices: &[&ArchetypeIndex], + archetype_indices: &[ArchetypeIndex], ) -> WorldResult>> { let archetypes = self.get_archetypes(archetype_indices)?; @@ -604,7 +587,7 @@ impl World { fn borrow_component_vecs_mut( &self, - archetype_indices: &[&ArchetypeIndex], + archetype_indices: &[ArchetypeIndex], ) -> WorldResult>> { let archetypes = self.get_archetypes(archetype_indices)?; @@ -619,7 +602,10 @@ impl World { fn panic_locked_component_vec() -> ! { let component_type_name = any::type_name::(); - panic!("Lock of ComponentVec<{component_type_name}> is already taken!") + panic!( + "Lock of ComponentVec<{}> is already taken!", + component_type_name + ) } impl Default for World { @@ -677,20 +663,13 @@ fn borrow_component_vec_mut( None } -fn find_intersecting_signature_indices( - signatures: Vec<&Vec>, -) -> Vec<&ArchetypeIndex> { - if let [first_signature, signatures @ ..] = &*signatures { - return first_signature - .iter() - .filter(|component_type| { - signatures - .iter() - .all(|other_signature| other_signature.contains(component_type)) - }) - .collect(); - } - unreachable!("Passing any vector always matches the if clause") +fn intersection_of_multiple_sets(sets: &[HashSet]) -> HashSet { + sets.get(0) + .unwrap_or(&HashSet::new()) + .iter() + .filter(move |value| sets[1..].iter().all(|set| set.contains(value))) + .cloned() + .collect() } type ComponentVecImpl = RwLock>>; @@ -703,7 +682,7 @@ trait ComponentVec: Debug + Send + Sync { fn stored_type(&self) -> TypeId; /// Returns the number of components stored in the component vector. fn len(&self) -> usize; - /// Removes the entity from the componet vector. + /// Removes the entity from the component vector. fn remove(&self, index: usize); } @@ -936,16 +915,16 @@ pub trait SystemParameters: Send + Sync { fn component_accesses() -> Vec; } -/// Something that can be passed to a `ecs::System`. +/// Something that can be passed to a [`System`]. pub(crate) trait SystemParameter: Send + Sync + Sized { /// Contains a borrow of components from `ecs::World`. type BorrowedData<'components>; - /// Borrows the collection of components of the given type from `ecs::World`. - fn borrow<'a>( - world: &'a World, - signature: &[TypeId], - ) -> SystemParameterResult>; + /// Borrows the collection of components of the given type from [`World`]. + fn borrow<'world>( + world: &'world World, + archetypes: &[ArchetypeIndex], + ) -> SystemParameterResult>; /// Fetches the parameter from the borrowed data for a given entity. /// # Safety @@ -953,14 +932,20 @@ pub(crate) trait SystemParameter: Send + Sync + Sized { unsafe fn fetch_parameter(borrowed: &mut Self::BorrowedData<'_>) -> Option>; /// A description of what data is accessed and how (read/write). - fn component_access() -> ComponentAccessDescriptor; + fn component_access() -> Option; - /// Returns the `TypeId` of the borrowed data. - fn signature() -> TypeId { - match Self::component_access() { - ComponentAccessDescriptor::Read(type_id, _) - | ComponentAccessDescriptor::Write(type_id, _) => type_id, - } + /// If the [`SystemParameter`] require that the system iterates over entities. + /// The system will only run once if all [`SystemParameter`]'s `iterates_over_entities` is `false`. + fn iterates_over_entities() -> bool; + + /// The `base_signature` is used for [`SystemParameter`]s that always require a component on the entity. + /// This will be `None` for binary/unary filters. + fn base_signature() -> Option; + + /// Perform a filter operation on a set of archetype indices. + /// The `universe` is a set with all archetype indices used by the `base_signature`. + fn filter(universe: &HashSet, _world: &World) -> HashSet { + universe.clone() } } @@ -991,12 +976,12 @@ impl SystemParameter for Read< Vec<(ComponentIndex, ReadComponentVec<'components, Component>)>, ); - fn borrow<'a>( - world: &'a World, - signature: &[TypeId], - ) -> SystemParameterResult> { + fn borrow<'world>( + world: &'world World, + archetypes: &[ArchetypeIndex], + ) -> SystemParameterResult> { let component_vecs = world - .borrow_component_vecs_with_signature::(signature) + .borrow_component_vecs::(archetypes) .map_err(SystemParameterError::BorrowComponentVecs)?; let component_vecs = component_vecs @@ -1029,8 +1014,16 @@ impl SystemParameter for Read< None } - fn component_access() -> ComponentAccessDescriptor { - ComponentAccessDescriptor::read::() + fn component_access() -> Option { + Some(ComponentAccessDescriptor::read::()) + } + + fn iterates_over_entities() -> bool { + true + } + + fn base_signature() -> Option { + Some(TypeId::of::()) } } @@ -1040,13 +1033,13 @@ impl SystemParameter for Write Vec<(ComponentIndex, WriteComponentVec<'components, Component>)>, ); - fn borrow<'a>( - world: &'a World, - signature: &[TypeId], - ) -> SystemParameterResult> { + fn borrow<'world>( + world: &'world World, + archetypes: &[ArchetypeIndex], + ) -> SystemParameterResult> { Ok((0, { let component_vecs = world - .borrow_component_vecs_with_signature_mut::(signature) + .borrow_component_vecs_mut::(archetypes) .map_err(SystemParameterError::BorrowComponentVecs)?; component_vecs @@ -1079,8 +1072,16 @@ impl SystemParameter for Write None } - fn component_access() -> ComponentAccessDescriptor { - ComponentAccessDescriptor::write::() + fn component_access() -> Option { + Some(ComponentAccessDescriptor::write::()) + } + + fn iterates_over_entities() -> bool { + true + } + + fn base_signature() -> Option { + Some(TypeId::of::()) } } @@ -1125,7 +1126,10 @@ macro_rules! impl_system_parameter_function { paste! { impl<$([]: SystemParameter,)*> SystemParameters for ($([],)*) { fn component_accesses() -> Vec { - vec![$([]::component_access(),)*] + [$([]::component_access(),)*] + .into_iter() + .flatten() + .collect() } } @@ -1133,20 +1137,38 @@ macro_rules! impl_system_parameter_function { for F where F: Fn($([],)*) + 'static, { fn run(&self, world: &World) -> SystemResult<()> { - let signature = vec![$(<[] as SystemParameter>::signature(),)*]; + let base_signature: Vec = [$([]::base_signature(),)*] + .into_iter() + .flatten() + .collect(); + + let universe = world.get_archetype_indices(&base_signature); + + let archetypes_indices: Vec<_> = intersection_of_multiple_sets(&[ + universe.clone(), + $([]::filter(&universe, world),)* + ]) + .into_iter() + .collect(); - $(let mut [] = <[] as SystemParameter>::borrow(world, &signature).map_err(SystemError::MissingParameter)?;)* + $(let mut [] = []::borrow(world, &archetypes_indices).map_err(SystemError::MissingParameter)?;)* // SAFETY: This is safe because the result from fetch_parameter will not outlive borrowed unsafe { - while let ($(Some([]),)*) = ( - $(<[] as SystemParameter>::fetch_parameter(&mut []),)* - ) { - if let ($(Some([]),)*) = ( - $([],)* + if $([]::iterates_over_entities() ||)* false { + while let ($(Some([]),)*) = ( + $([]::fetch_parameter(&mut []),)* ) { - self($([],)*); + if let ($(Some([]),)*) = ( + $([],)* + ) { + self($([],)*); + } } + } else if let ($(Some(Some([])),)*) = ( + $([]::fetch_parameter(&mut []),)* + ) { + self($([],)*); } } Ok(()) @@ -1197,6 +1219,40 @@ mod tests { #[derive(Debug)] struct C; + trait BorrowComponentVecsWithSignature { + fn borrow_component_vecs_with_signature( + &self, + signature: &[TypeId], + ) -> WorldResult>>; + + fn borrow_component_vecs_with_signature_mut( + &self, + signature: &[TypeId], + ) -> WorldResult>>; + } + + impl BorrowComponentVecsWithSignature for World { + fn borrow_component_vecs_with_signature( + &self, + signature: &[TypeId], + ) -> WorldResult>> { + let archetype_indices: Vec<_> = + self.get_archetype_indices(signature).into_iter().collect(); + self.borrow_component_vecs(&archetype_indices) + } + + fn borrow_component_vecs_with_signature_mut< + ComponentType: Debug + Send + Sync + 'static, + >( + &self, + signature: &[TypeId], + ) -> WorldResult>> { + let archetype_indices: Vec<_> = + self.get_archetype_indices(signature).into_iter().collect(); + self.borrow_component_vecs_mut(&archetype_indices) + } + } + #[test] #[should_panic(expected = "Lock of ComponentVec is already taken!")] fn world_panics_when_trying_to_mutably_borrow_same_components_twice() { @@ -1482,7 +1538,7 @@ mod tests { eprintln!("vecs_u32 = {vecs_u32:#?}"); // Assert // Collect values from vecs - let result: Vec = vecs_u32 + let result: HashSet = vecs_u32 .iter() .flat_map(|component_vec| { component_vec @@ -1494,7 +1550,7 @@ mod tests { .collect(); println!("{result:?}"); - assert_eq!(result, vec![2, 4, 6]) + assert_eq!(result, HashSet::from([2, 4, 6])) } #[test] @@ -1529,13 +1585,14 @@ mod tests { // Arrange let world = setup_world_with_three_entities_and_components(); - let mut result: Vec = vec![]; + let mut result: HashSet = HashSet::new(); - let mut borrowed = as SystemParameter>::borrow( - &world, - &[ as SystemParameter>::signature()], - ) - .unwrap(); + let archetypes: Vec = world + .get_archetype_indices(&[TypeId::of::()]) + .into_iter() + .collect(); + + let mut borrowed = as SystemParameter>::borrow(&world, &archetypes).unwrap(); // SAFETY: This is safe because the result from fetch_parameter will not outlive borrowed unsafe { @@ -1543,14 +1600,12 @@ mod tests { as SystemParameter>::fetch_parameter(&mut borrowed) { if let Some(parameter) = parameter { - result.push(*parameter); + result.insert(*parameter); } } } - println!("{:?}", result); - - assert_eq!(result, vec![2, 4, 6]) + assert_eq!(result, HashSet::from([2, 4, 6])) } #[test] @@ -1651,11 +1706,14 @@ mod tests { expected_value: Vec, ) { // Construct test values, to avoid upsetting Rust and test_case - let borrowed_test_vecs: Vec<&Vec> = test_vecs.iter().collect(); - let borrowed_expected_value: Vec<&usize> = expected_value.iter().collect(); + let borrowed_test_vecs: Vec> = test_vecs + .iter() + .map(|vec| HashSet::from_iter(vec.clone())) + .collect(); + let borrowed_expected_value: HashSet = expected_value.into_iter().collect(); // Perform intersection operation - let result = find_intersecting_signature_indices(borrowed_test_vecs); + let result = intersection_of_multiple_sets(&borrowed_test_vecs); // Assert intersection result equals expected value assert_eq!(result, borrowed_expected_value); From f800c797512ce312fa9047e59680427eff9bd638 Mon Sep 17 00:00:00 2001 From: Edvin Nillsson Date: Thu, 30 Mar 2023 11:59:50 +0200 Subject: [PATCH 09/12] Replace .unwrap() with ?, rename variable --- crates/ecs/src/filter.rs | 3 +-- crates/ecs/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ecs/src/filter.rs b/crates/ecs/src/filter.rs index 12cd1b5..b2b43fd 100644 --- a/crates/ecs/src/filter.rs +++ b/crates/ecs/src/filter.rs @@ -305,8 +305,7 @@ mod tests { .into_iter() .collect(); - let mut borrowed = - as SystemParameter>::borrow(&world, &archetypes).unwrap(); + let mut borrowed = as SystemParameter>::borrow(&world, &archetypes)?; // SAFETY: This is safe because the result from fetch_parameter will not outlive borrowed unsafe { diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index cf07883..180254d 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -672,7 +672,7 @@ fn intersection_of_multiple_sets(sets: &[HashSet]) -> H sets.get(0) .unwrap_or(&HashSet::new()) .iter() - .filter(move |value| sets[1..].iter().all(|set| set.contains(value))) + .filter(move |element| sets[1..].iter().all(|set| set.contains(element))) .cloned() .collect() } From fdfb89c336678f2bd82c442860ab47f60d401acb Mon Sep 17 00:00:00 2001 From: Edvin Nillsson Date: Thu, 30 Mar 2023 12:01:05 +0200 Subject: [PATCH 10/12] Add doc-comment clarifying usage of base_signature --- crates/ecs/src/systems/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/ecs/src/systems/mod.rs b/crates/ecs/src/systems/mod.rs index fb495d0..57cfa23 100644 --- a/crates/ecs/src/systems/mod.rs +++ b/crates/ecs/src/systems/mod.rs @@ -235,8 +235,15 @@ pub(crate) trait SystemParameter: Send + Sync + Sized { /// The system will only run once if all [`SystemParameter`]'s `iterates_over_entities` is `false`. fn iterates_over_entities() -> bool; - /// The `base_signature` is used for [`SystemParameter`]s that always require a component on the entity. - /// This will be `None` for binary/unary filters. + /// The `base_signature` is used for [`SystemParameter`]s that always require a component on the + /// entity. This will be `None` for binary/unary filters. + /// + /// For example, if `(Read, With, With)` is queried, then the `base_signature` will be + /// the [`TypeId`]s of `{A, B, C}`. If instead `(Read, Or, With>)` is queried, + /// then it will just be the [`TypeId`]s of `{A}`. A set of the archetype indices that includes + /// all components of the `base_signature` is created and this set is called the `universe`. + /// The queried archetypes is find by taking the intersection of the `universe` and the filtered + /// versions of the `universe` using the `filter` function. fn base_signature() -> Option; /// Perform a filter operation on a set of archetype indices. From 31922bae8820cb12e05d936c32b34fb4b80d0b48 Mon Sep 17 00:00:00 2001 From: Edvin Nilsson <42613683+EdvinNilsson@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:49:01 +0200 Subject: [PATCH 11/12] Fix grammar mistake Co-authored-by: Martin --- crates/ecs/src/systems/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ecs/src/systems/mod.rs b/crates/ecs/src/systems/mod.rs index 57cfa23..1c0e7c3 100644 --- a/crates/ecs/src/systems/mod.rs +++ b/crates/ecs/src/systems/mod.rs @@ -242,7 +242,7 @@ pub(crate) trait SystemParameter: Send + Sync + Sized { /// the [`TypeId`]s of `{A, B, C}`. If instead `(Read, Or, With>)` is queried, /// then it will just be the [`TypeId`]s of `{A}`. A set of the archetype indices that includes /// all components of the `base_signature` is created and this set is called the `universe`. - /// The queried archetypes is find by taking the intersection of the `universe` and the filtered + /// The queried archetypes are found by taking the intersection of the `universe` and the filtered /// versions of the `universe` using the `filter` function. fn base_signature() -> Option; From 415694415ad5e36a45b250ab595332dd270831c0 Mon Sep 17 00:00:00 2001 From: Edvin Nillsson Date: Thu, 30 Mar 2023 14:07:53 +0200 Subject: [PATCH 12/12] Move filter predicate to variable --- crates/ecs/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ecs/src/lib.rs b/crates/ecs/src/lib.rs index 180254d..3d6719b 100644 --- a/crates/ecs/src/lib.rs +++ b/crates/ecs/src/lib.rs @@ -669,10 +669,12 @@ fn borrow_component_vec_mut( } fn intersection_of_multiple_sets(sets: &[HashSet]) -> HashSet { + let element_overlaps_with_all_other_sets = + move |element: &&T| sets[1..].iter().all(|set| set.contains(element)); sets.get(0) .unwrap_or(&HashSet::new()) .iter() - .filter(move |element| sets[1..].iter().all(|set| set.contains(element))) + .filter(element_overlaps_with_all_other_sets) .cloned() .collect() }