From 14fea0879d39b016f95d81721e37f41694f59e9c Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Tue, 5 Apr 2022 23:58:25 +0200 Subject: [PATCH 1/6] Add macro to (almost) make data querying safe --- ecs/src/lib.rs | 41 +++++++++++++++++++++++++++++++ ecs/src/query.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index a4be1c0..623ede4 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -3,6 +3,7 @@ pub mod component; mod entity; mod error; +#[macro_use] mod query; mod world; @@ -508,6 +509,46 @@ mod tests { mem::drop(r); } + #[test] + fn type_safe_macros() { + let mut world = World::default(); + struct Name(String); + struct Speed(f32); + let sanic = world.spawn(); + world.add(sanic, Name("Sanic".into())); + world.add(sanic, Speed(100.0)); + let mario = world.spawn(); + world.add(mario, Name("Mario".into())); + world.add(mario, Speed(200.0)); // copilot thinks mario is faster than sanic + + let mut cheeky_breeky = None; + query_iter!(world, (name: Name, speed: mut Speed) => { + match name.0.as_ref() { + "Mario" => assert_eq!(speed.0, 200.0), + "Sanic" => { + assert_eq!(speed.0, 100.0); + speed.0 = 300.0; // copilot thinks he's faster than mario + cheeky_breeky = Some(speed); // TODO: this should not be possible + } + _ => panic!("Unexpected name"), + } + }); + + query_iter!(world, (name: Name, speed: Speed) => { + match name.0.as_ref() { + "Mario" => assert_eq!(speed.0, 200.0), + "Sanic" => { + assert_eq!(speed.0, 300.0); + if let Some(Speed(s)) = cheeky_breeky { + *s += 1.; + } + assert_eq!(speed.0, 301.0); + } + _ => panic!("Unexpected name"), + } + }); + } + #[test] fn iterate_over_query() { let mut world = World::default(); diff --git a/ecs/src/query.rs b/ecs/src/query.rs index 9f5ef8e..c996345 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -5,6 +5,70 @@ use crate::{ BorrowMutError, Entity, }; +#[macro_export] +macro_rules! _query_definition { + ( $world:expr, $vec:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => {{ + $vec.push(ComponentQuery { + id: $world.component_id::<$type>().unwrap(), + mutable: false, + }); + _query_definition!($world, $vec, ($($tail)*)); + }}; + ( $world:expr, $vec:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => {{ + $vec.push(ComponentQuery { + id: $world.component_id::<$type>().unwrap(), + mutable: true, + }); + _query_definition!($world, $vec, ($($tail)*)); + }}; + ( $world:expr, $vec:expr, ($name:ident: $type:ty) ) => {{ + $vec.push(ComponentQuery { + id: $world.component_id::<$type>().unwrap(), + mutable: false, + }); + }}; + ( $world:expr, $vec:expr, ($name:ident: mut $type:ty) ) => {{ + $vec.push(ComponentQuery { + id: $world.component_id::<$type>().unwrap(), + mutable: true, + }); + }}; +} + +#[macro_export] +macro_rules! _query_defvars { + ( $comps:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => { + let $name = unsafe { $comps[0].cast::<$type>().as_ref() }; + _query_defvars!($comps[1..], ($($tail)*)); + }; + ( $comps:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => { + let $name = unsafe { $comps[0].cast::<$type>().as_mut() }; + _query_defvars!($comps[1..], ($($tail)*)); + }; + ( $comps:expr, ($name:ident: $type:ty) ) => { + let $name = unsafe { $comps[0].cast::<$type>().as_ref() }; + }; + ( $comps:expr, ($name:ident: mut $type:ty) ) => { + let $name = unsafe { $comps[0].cast::<$type>().as_mut() }; + }; +} + +#[macro_export] +macro_rules! query_iter { + ( $world:expr, ($($query:tt)*) => $body:block ) => {{ + let mut v = vec![]; + _query_definition!($world, v, ($($query)*)); + let q = Query::new(v).expect("Query violates rusts borrow rules"); + + let mut res = $world.query(&q); + + for comps in unsafe { res.iter() } { + $crate::_query_defvars!(comps, ($($query)*)); + $body + } + }}; +} + /// Represents a valid query for components without multiple mutable access to the same type of /// component. /// NOTE: there's currently no way of for example having one query for `mut A` on entities with a From 525bce0cd3833436f2f256660f59d99f49dbcc7b Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Thu, 7 Apr 2022 00:04:56 +0200 Subject: [PATCH 2/6] Make `query_iter!` macro memory safe --- ecs/src/lib.rs | 8 +------- ecs/src/query.rs | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 623ede4..ec0bfd3 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -9,7 +9,7 @@ mod world; pub use entity::{Entities, Entity}; pub use error::BorrowMutError; -pub use query::{ComponentQuery, Query}; +pub use query::{as_mut_lt, as_ref_lt, ComponentQuery, Query}; pub use world::World; #[cfg(test)] @@ -521,14 +521,12 @@ mod tests { world.add(mario, Name("Mario".into())); world.add(mario, Speed(200.0)); // copilot thinks mario is faster than sanic - let mut cheeky_breeky = None; query_iter!(world, (name: Name, speed: mut Speed) => { match name.0.as_ref() { "Mario" => assert_eq!(speed.0, 200.0), "Sanic" => { assert_eq!(speed.0, 100.0); speed.0 = 300.0; // copilot thinks he's faster than mario - cheeky_breeky = Some(speed); // TODO: this should not be possible } _ => panic!("Unexpected name"), } @@ -539,10 +537,6 @@ mod tests { "Mario" => assert_eq!(speed.0, 200.0), "Sanic" => { assert_eq!(speed.0, 300.0); - if let Some(Speed(s)) = cheeky_breeky { - *s += 1.; - } - assert_eq!(speed.0, 301.0); } _ => panic!("Unexpected name"), } diff --git a/ecs/src/query.rs b/ecs/src/query.rs index c996345..0e87465 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -5,6 +5,14 @@ use crate::{ BorrowMutError, Entity, }; +pub unsafe fn as_ref_lt<'a, T>(_lifetime: &'a (), ptr: NonNull) -> &'a T { + ptr.as_ref() +} + +pub unsafe fn as_mut_lt<'a, T>(_lifetime: &'a (), mut ptr: NonNull) -> &'a mut T { + ptr.as_mut() +} + #[macro_export] macro_rules! _query_definition { ( $world:expr, $vec:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => {{ @@ -37,19 +45,19 @@ macro_rules! _query_definition { #[macro_export] macro_rules! _query_defvars { - ( $comps:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => { - let $name = unsafe { $comps[0].cast::<$type>().as_ref() }; - _query_defvars!($comps[1..], ($($tail)*)); + ( $comps:expr, $lt:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => { + let $name = unsafe { $crate::query::as_ref_lt($lt, $comps[0].cast::<$type>()) }; + _query_defvars!($comps[1..], $lt, ($($tail)*)); }; - ( $comps:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => { - let $name = unsafe { $comps[0].cast::<$type>().as_mut() }; - _query_defvars!($comps[1..], ($($tail)*)); + ( $comps:expr, $lt:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => { + let $name = unsafe { $crate::query::as_mut_lt($lt, $comps[0].cast::<$type>()) }; + _query_defvars!($comps[1..], $lt, ($($tail)*)); }; - ( $comps:expr, ($name:ident: $type:ty) ) => { - let $name = unsafe { $comps[0].cast::<$type>().as_ref() }; + ( $comps:expr, $lt:expr, ($name:ident: $type:ty) ) => { + let $name = unsafe { $crate::query::as_ref_lt($lt, $comps[0].cast::<$type>()) }; }; - ( $comps:expr, ($name:ident: mut $type:ty) ) => { - let $name = unsafe { $comps[0].cast::<$type>().as_mut() }; + ( $comps:expr, $lt:expr, ($name:ident: mut $type:ty) ) => { + let $name = unsafe { $crate::query::as_mut_lt($lt, $comps[0].cast::<$type>()) }; }; } @@ -63,7 +71,8 @@ macro_rules! query_iter { let mut res = $world.query(&q); for comps in unsafe { res.iter() } { - $crate::_query_defvars!(comps, ($($query)*)); + let lt = (); + $crate::_query_defvars!(comps, <, ($($query)*)); $body } }}; From 5dced7d9ec97106120a81e7c9687ef5104ce4b2d Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Mon, 18 Apr 2022 12:33:34 +0200 Subject: [PATCH 3/6] Add documentation to helper functions --- ecs/src/query.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ecs/src/query.rs b/ecs/src/query.rs index 0e87465..e044c28 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -5,10 +5,20 @@ use crate::{ BorrowMutError, Entity, }; +/// Casts `ptr` to a reference with the lifetime `'a`. +/// # Safety +/// It is the responsibility of the caller to ensure that the lifetime `'a` outlives +/// the lifetime of the data pointed to by `ptr`. +#[allow(clippy::needless_lifetimes)] pub unsafe fn as_ref_lt<'a, T>(_lifetime: &'a (), ptr: NonNull) -> &'a T { ptr.as_ref() } +/// Casts `ptr` to a mutable reference with the lifetime `'a`. +/// # Safety +/// It is the responsibility of the caller to ensure that the lifetime `'a` outlives +/// the lifetime of the data pointed to by `ptr`. +#[allow(clippy::mut_from_ref, clippy::needless_lifetimes)] pub unsafe fn as_mut_lt<'a, T>(_lifetime: &'a (), mut ptr: NonNull) -> &'a mut T { ptr.as_mut() } From 5d716e47533b6d7bd9f8be55f477a97b7444a7c2 Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Mon, 18 Apr 2022 13:28:15 +0200 Subject: [PATCH 4/6] Optionally get handle to entity with query --- ecs/src/entity.rs | 5 ++ ecs/src/lib.rs | 31 +++++++++--- ecs/src/query.rs | 121 ++++++++++++++++++++++------------------------ ecs/src/world.rs | 7 ++- 4 files changed, 92 insertions(+), 72 deletions(-) diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs index e40ec35..36f711e 100644 --- a/ecs/src/entity.rs +++ b/ecs/src/entity.rs @@ -119,6 +119,11 @@ impl<'e> Iter<'e> { unused_ids: entities.unused_ids.iter().copied().collect(), } } + + /// Get the iter's entities. + pub fn entities(&self) -> &Entities { + self.entities + } } impl<'e> Iterator for Iter<'e> { diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index ec0bfd3..5d78779 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -387,12 +387,12 @@ mod tests { ]) .unwrap(); { - let mut res = world.query(&usize_query); + let res = world.query(&usize_query); assert_eq!(*unsafe { res.get(a)[0].cast::().as_ref() }, 0); assert_eq!(*unsafe { res.get(b)[0].cast::().as_ref() }, 1); } { - let mut res = world.query(&both_query); + let res = world.query(&both_query); assert!(unsafe { res.try_get(a) }.is_none()); let (int, float) = unsafe { if let [int, float] = res.get(b)[..] { @@ -405,7 +405,7 @@ mod tests { assert_eq!(2., *float); } { - let mut res = world.query(&usize_query); + let res = world.query(&usize_query); assert_eq!(*unsafe { res.get(a)[0].cast::().as_ref() }, 0); assert_eq!(*unsafe { res.get(b)[0].cast::().as_ref() }, 3); } @@ -532,15 +532,32 @@ mod tests { } }); - query_iter!(world, (name: Name, speed: Speed) => { + query_iter!(world, (entity: Entity, name: Name, speed: Speed) => { match name.0.as_ref() { - "Mario" => assert_eq!(speed.0, 200.0), + "Mario" => { + assert_eq!(entity, mario); + assert_eq!(speed.0, 200.0) + } "Sanic" => { + assert_eq!(entity, sanic); assert_eq!(speed.0, 300.0); } _ => panic!("Unexpected name"), } }); + + let mut found_sanic = false; + let mut found_mario = false; + query_iter!(world, (entity: Entity) => { + if found_sanic { + assert_eq!(entity, mario); + found_mario = true; + } else { + assert_eq!(entity, sanic); + found_sanic = true; + } + }); + assert!(found_sanic && found_mario); } #[test] @@ -570,7 +587,7 @@ mod tests { .unwrap(); let mut q = world.query(&q); for (pos, vel) in unsafe { - q.iter().map(|comps| { + q.iter().map(|(_e, comps)| { if let [pos, vel] = comps[..] { ( pos.cast::().as_mut(), @@ -593,7 +610,7 @@ mod tests { let mut q = world.query(&q); for (i, pos) in unsafe { q.iter() - .map(|comps| { + .map(|(_e, comps)| { if let [pos] = comps[..] { pos.cast::().as_ref() } else { diff --git a/ecs/src/query.rs b/ecs/src/query.rs index e044c28..215c030 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -1,8 +1,9 @@ -use std::{collections::HashSet, marker::PhantomData, ptr::NonNull}; +use std::{collections::HashSet, ptr::NonNull}; use crate::{ - component::{ComponentEntryRef, ComponentId, ComponentRegistry}, - BorrowMutError, Entity, + component::{ComponentEntryRef, ComponentId}, + entity::Iter as EntityIter, + BorrowMutError, Entity, World, }; /// Casts `ptr` to a reference with the lifetime `'a`. @@ -25,6 +26,9 @@ pub unsafe fn as_mut_lt<'a, T>(_lifetime: &'a (), mut ptr: NonNull) -> &'a mu #[macro_export] macro_rules! _query_definition { + ( $world:expr, $vec:expr, ($name:ident: Entity, $($tail:tt)*) ) => {{ + _query_definition!($world, $vec, ($($tail)*)); + }}; ( $world:expr, $vec:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => {{ $vec.push(ComponentQuery { id: $world.component_id::<$type>().unwrap(), @@ -39,6 +43,9 @@ macro_rules! _query_definition { }); _query_definition!($world, $vec, ($($tail)*)); }}; + + // Last entry + ( $world:expr, $vec:expr, ($name:ident: Entity) ) => { }; ( $world:expr, $vec:expr, ($name:ident: $type:ty) ) => {{ $vec.push(ComponentQuery { id: $world.component_id::<$type>().unwrap(), @@ -55,18 +62,27 @@ macro_rules! _query_definition { #[macro_export] macro_rules! _query_defvars { - ( $comps:expr, $lt:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => { + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: Entity, $($tail:tt)*) ) => { + let $name = $entity; + _query_defvars!($comps[..], $lt, $entity, ($($tail)*)); + }; + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: $type:ty, $($tail:tt)*) ) => { let $name = unsafe { $crate::query::as_ref_lt($lt, $comps[0].cast::<$type>()) }; - _query_defvars!($comps[1..], $lt, ($($tail)*)); + _query_defvars!($comps[1..], $lt, $entity, ($($tail)*)); }; - ( $comps:expr, $lt:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => { + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: mut $type:ty, $($tail:tt)*) ) => { let $name = unsafe { $crate::query::as_mut_lt($lt, $comps[0].cast::<$type>()) }; - _query_defvars!($comps[1..], $lt, ($($tail)*)); + _query_defvars!($comps[1..], $lt, $entity, ($($tail)*)); + }; + + // Last entry + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: Entity) ) => { + let $name = $entity; }; - ( $comps:expr, $lt:expr, ($name:ident: $type:ty) ) => { + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: $type:ty) ) => { let $name = unsafe { $crate::query::as_ref_lt($lt, $comps[0].cast::<$type>()) }; }; - ( $comps:expr, $lt:expr, ($name:ident: mut $type:ty) ) => { + ( $comps:expr, $lt:expr, $entity:expr, ($name:ident: mut $type:ty) ) => { let $name = unsafe { $crate::query::as_mut_lt($lt, $comps[0].cast::<$type>()) }; }; } @@ -74,15 +90,17 @@ macro_rules! _query_defvars { #[macro_export] macro_rules! query_iter { ( $world:expr, ($($query:tt)*) => $body:block ) => {{ + #[allow(unused_mut)] let mut v = vec![]; _query_definition!($world, v, ($($query)*)); let q = Query::new(v).expect("Query violates rusts borrow rules"); let mut res = $world.query(&q); - for comps in unsafe { res.iter() } { + #[allow(unused_variables)] + for (e, comps) in unsafe { res.iter() } { let lt = (); - $crate::_query_defvars!(comps, <, ($($query)*)); + $crate::_query_defvars!(comps, <, e, ($($query)*)); $body } }}; @@ -102,7 +120,6 @@ pub struct Query { pub struct ComponentQuery { pub id: ComponentId, pub mutable: bool, - // TODO: add optional queries: `optional: bool,` } impl Query { @@ -133,21 +150,17 @@ impl Query { } #[derive(Debug)] -pub struct QueryResponse<'r, 'q> { - _world_marker: PhantomData<&'r ComponentRegistry>, +pub struct QueryResponse<'w, 'q> { + world: &'w World, entries: Vec, query: &'q Query, } -impl<'r, 'q> QueryResponse<'r, 'q> { - pub(crate) fn new( - _registry: &'r ComponentRegistry, - query: &'q Query, - entries: Vec, - ) -> Self { +impl<'w, 'q> QueryResponse<'w, 'q> { + pub(crate) fn new(world: &'w World, query: &'q Query, entries: Vec) -> Self { debug_assert!(query.components().len() == entries.len()); Self { - _world_marker: PhantomData, + world, entries, query, } @@ -156,7 +169,7 @@ impl<'r, 'q> QueryResponse<'r, 'q> { /// Same as `try_get` but panics if `None` would be returned. /// # Safety /// See documentation for `try_get` - pub unsafe fn get(&mut self, entity: Entity) -> Vec> { + pub unsafe fn get(&self, entity: Entity) -> Vec> { self.try_get(entity) .expect("The given entity does not match the query or has been despawned") } @@ -168,67 +181,47 @@ impl<'r, 'q> QueryResponse<'r, 'q> { /// All pointers returned are technically mutable **BUT** modifying the pointers to components /// not marked as mutable in the query is undefined behaviour. /// The pointers must not outlive this `QueryResponse` - pub unsafe fn try_get(&mut self, entity: Entity) -> Option>> { + pub unsafe fn try_get(&self, entity: Entity) -> Option>> { self.try_get_by_index(entity.get_id_unchecked()) } - unsafe fn try_get_by_index(&mut self, index: u32) -> Option>> { + unsafe fn try_get_by_index(&self, index: u32) -> Option>> { let mut res = Vec::with_capacity(self.entries.len()); - for (e, cq) in self.entries.iter_mut().zip(self.query.components().iter()) { - res.push(if cq.mutable { - NonNull::new(e.get_mut().storage.get_mut_ptr(index as usize))? - } else { - NonNull::new(e.get().storage.get_ptr(index as usize) as *mut _)? - }); + for (e, _) in self.entries.iter().zip(self.query.components().iter()) { + res.push(NonNull::new( + e.get().storage.get_ptr(index as usize) as *mut _ + )?); } Some(res) } - /// Returns the last index of an entity that has at least one component in the query. There - /// might not actually be a hit for this query at this index, but there is definitly no hits - /// after this index. - fn last_index_worth_checking(&self) -> Option { - self.entries - .iter() - .flat_map(|e| e.get().storage.last_set_index()) - .max() - .map(|max| max as u32) - } - - pub unsafe fn iter<'a>(&'a mut self) -> Iter<'a, 'r, 'q> { - Iter::new(self, self.last_index_worth_checking()) + pub unsafe fn iter<'a>(&'a mut self) -> Iter<'a, 'w, 'q> { + Iter::new(self) } } -pub struct Iter<'a, 'r, 'q> { - index: u32, - last: Option, - res: &'a mut QueryResponse<'r, 'q>, +pub struct Iter<'a, 'w, 'q> { + res: &'a QueryResponse<'w, 'q>, + entity_iter: EntityIter<'w>, } -impl<'a, 'r, 'q> Iter<'a, 'r, 'q> { - pub fn new(res: &'a mut QueryResponse<'r, 'q>, last: Option) -> Self { - Self { - index: 0, - last, - res, - } +impl<'a, 'w, 'q> Iter<'a, 'w, 'q> { + pub fn new(res: &'a mut QueryResponse<'w, 'q>) -> Self { + let mut entity_iter = res.world.entities().iter(); + entity_iter.next(); // Skip the resource entity. + Self { res, entity_iter } } } // TODO: for sparse components this could be optimized impl<'a, 'r, 'q> Iterator for Iter<'a, 'r, 'q> { - type Item = Vec>; + type Item = (Entity, Vec>); fn next(&mut self) -> Option { - while self.index < self.last? { - let index = self.index; - self.index += 1; - let res = unsafe { self.res.try_get_by_index(index) }; - if res.is_some() { - return res; - } - } - None + self.entity_iter.next().and_then(|e| unsafe { + self.res + .try_get_by_index(self.entity_iter.entities().id(e).unwrap()) + .map(|comps| (e, comps)) + }) } } diff --git a/ecs/src/world.rs b/ecs/src/world.rs index ab1c15d..5e46fcb 100644 --- a/ecs/src/world.rs +++ b/ecs/src/world.rs @@ -119,7 +119,7 @@ impl World { None => return Err(BorrowMutError::new(c.id)), } } - Ok(QueryResponse::new(&self.component_registry, query, entries)) + Ok(QueryResponse::new(&self, query, entries)) } /// Tries to query for a set of components. If thats not possible (see `try_query`) this @@ -147,4 +147,9 @@ impl World { .get_mut(id as usize) }) } + + /// Get a reference to the world's entities. + pub fn entities(&self) -> &Entities { + &self.entities + } } From e92737bab1878697a01303ec9c8abc8918dd3716 Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Mon, 18 Apr 2022 13:31:28 +0200 Subject: [PATCH 5/6] Make clippy happy --- ecs/src/world.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecs/src/world.rs b/ecs/src/world.rs index 5e46fcb..b3b4582 100644 --- a/ecs/src/world.rs +++ b/ecs/src/world.rs @@ -119,7 +119,7 @@ impl World { None => return Err(BorrowMutError::new(c.id)), } } - Ok(QueryResponse::new(&self, query, entries)) + Ok(QueryResponse::new(self, query, entries)) } /// Tries to query for a set of components. If thats not possible (see `try_query`) this From 6d323acf97d1f032b6ff878046a47adc7bbaa26a Mon Sep 17 00:00:00 2001 From: mathiasmagnusson Date: Wed, 4 May 2022 19:31:17 +0200 Subject: [PATCH 6/6] Fix weird mutability --- ecs/src/lib.rs | 6 +++--- ecs/src/query.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs index 5d78779..1294fc1 100644 --- a/ecs/src/lib.rs +++ b/ecs/src/lib.rs @@ -387,12 +387,12 @@ mod tests { ]) .unwrap(); { - let res = world.query(&usize_query); + let mut res = world.query(&usize_query); assert_eq!(*unsafe { res.get(a)[0].cast::().as_ref() }, 0); assert_eq!(*unsafe { res.get(b)[0].cast::().as_ref() }, 1); } { - let res = world.query(&both_query); + let mut res = world.query(&both_query); assert!(unsafe { res.try_get(a) }.is_none()); let (int, float) = unsafe { if let [int, float] = res.get(b)[..] { @@ -405,7 +405,7 @@ mod tests { assert_eq!(2., *float); } { - let res = world.query(&usize_query); + let mut res = world.query(&usize_query); assert_eq!(*unsafe { res.get(a)[0].cast::().as_ref() }, 0); assert_eq!(*unsafe { res.get(b)[0].cast::().as_ref() }, 3); } diff --git a/ecs/src/query.rs b/ecs/src/query.rs index 215c030..58f6c06 100644 --- a/ecs/src/query.rs +++ b/ecs/src/query.rs @@ -169,7 +169,7 @@ impl<'w, 'q> QueryResponse<'w, 'q> { /// Same as `try_get` but panics if `None` would be returned. /// # Safety /// See documentation for `try_get` - pub unsafe fn get(&self, entity: Entity) -> Vec> { + pub unsafe fn get(&mut self, entity: Entity) -> Vec> { self.try_get(entity) .expect("The given entity does not match the query or has been despawned") } @@ -181,11 +181,11 @@ impl<'w, 'q> QueryResponse<'w, 'q> { /// All pointers returned are technically mutable **BUT** modifying the pointers to components /// not marked as mutable in the query is undefined behaviour. /// The pointers must not outlive this `QueryResponse` - pub unsafe fn try_get(&self, entity: Entity) -> Option>> { + pub unsafe fn try_get(&mut self, entity: Entity) -> Option>> { self.try_get_by_index(entity.get_id_unchecked()) } - unsafe fn try_get_by_index(&self, index: u32) -> Option>> { + unsafe fn try_get_by_index(&mut self, index: u32) -> Option>> { let mut res = Vec::with_capacity(self.entries.len()); for (e, _) in self.entries.iter().zip(self.query.components().iter()) { res.push(NonNull::new( @@ -201,7 +201,7 @@ impl<'w, 'q> QueryResponse<'w, 'q> { } pub struct Iter<'a, 'w, 'q> { - res: &'a QueryResponse<'w, 'q>, + res: &'a mut QueryResponse<'w, 'q>, entity_iter: EntityIter<'w>, }