From 2f336d604485b98cc81e821596eb649037eb1455 Mon Sep 17 00:00:00 2001 From: Frizi Date: Sat, 24 Jul 2021 20:23:28 +0200 Subject: [PATCH 1/5] implement WorldCell queries --- crates/bevy_ecs/src/world/mod.rs | 4 +- crates/bevy_ecs/src/world/world_cell.rs | 533 +++++++++++++++++++++--- 2 files changed, 468 insertions(+), 69 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e9f56a58a8ec9..cb99e314373d6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -77,7 +77,7 @@ pub struct World { pub(crate) bundles: Bundles, pub(crate) removed_components: SparseSet>, /// Access cache used by [WorldCell]. - pub(crate) archetype_component_access: ArchetypeComponentAccess, + pub(crate) world_cell_state: WorldCellState, main_thread_validator: MainThreadValidator, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: u32, @@ -93,7 +93,7 @@ impl Default for World { storages: Default::default(), bundles: Default::default(), removed_components: Default::default(), - archetype_component_access: Default::default(), + world_cell_state: WorldCellState::new(), main_thread_validator: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index e41954f394b80..d2103db734d7b 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -1,12 +1,16 @@ use crate::{ archetype::ArchetypeComponentId, + component::ComponentId, + query::{FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, storage::SparseSet, system::Resource, world::{Mut, World}, }; use std::{ - any::TypeId, - cell::RefCell, + any::{Any, TypeId}, + cell::{Cell, RefCell}, + collections::HashMap, + marker::PhantomData, ops::{Deref, DerefMut}, rc::Rc, }; @@ -15,23 +19,81 @@ use std::{ /// World in a way that violates Rust's mutability rules will panic thanks to runtime checks. pub struct WorldCell<'w> { pub(crate) world: &'w mut World, - pub(crate) access: Rc>, + pub(crate) state: WorldCellState, } -pub(crate) struct ArchetypeComponentAccess { - access: SparseSet, +struct QueryCacheEntry { + alive_count: Cell, + query: Q, } -impl Default for ArchetypeComponentAccess { - fn default() -> Self { +impl QueryCacheEntry { + fn alive_filtered_access(&self) -> Option<&FilteredAccess> { + if self.alive_count.get() > 0 { + Some(self.query.component_access()) + } else { + None + } + } +} + +trait DynQueryState: Any { + fn component_access(&self) -> &FilteredAccess; + fn as_any(&self) -> &dyn Any; +} + +impl DynQueryState for QueryState +where + F::Fetch: FilterFetch, +{ + fn component_access(&self) -> &FilteredAccess { + &self.component_access + } + fn as_any(&self) -> &dyn Any { + self + } +} + +pub(crate) struct WorldCellState { + resource_access: RefCell, + // component_access: RefCell, + query_cache: HashMap, fxhash::FxBuildHasher>, +} + +impl WorldCellState { + // cannot be const because of hashmap, but should still be optimized out + #[inline] + pub fn new() -> Self { Self { - access: SparseSet::new(), + resource_access: RefCell::new(ArchetypeComponentAccess::new()), + // component_access: RefCell::new(ComponentAccess::new()), + query_cache: HashMap::default(), + } + } + + fn get_live_query_conflicts_filtered( + &self, + filtered_access: &FilteredAccess, + ) -> Vec { + for query in self.query_cache.values() { + if let Some(current_filtered_access) = query.alive_filtered_access() { + if !current_filtered_access.is_compatible(filtered_access) { + return current_filtered_access + .access() + .get_conflicts(filtered_access.access()); + } + } } + Vec::new() } } -const UNIQUE_ACCESS: usize = 0; -const BASE_ACCESS: usize = 1; +pub(crate) struct ArchetypeComponentAccess { + access: SparseSet, +} + +const UNIQUE_ACCESS: u32 = 0; +const BASE_ACCESS: u32 = 1; impl ArchetypeComponentAccess { const fn new() -> Self { Self { @@ -72,25 +134,28 @@ impl ArchetypeComponentAccess { impl<'w> Drop for WorldCell<'w> { fn drop(&mut self) { - let mut access = self.access.borrow_mut(); // give world ArchetypeComponentAccess back to reuse allocations - let _ = std::mem::swap(&mut self.world.archetype_component_access, &mut *access); + let _ = std::mem::swap(&mut self.world.world_cell_state, &mut self.state); } } -pub struct WorldBorrow<'w, T> { +pub struct WorldCellRes<'w, T> { value: &'w T, archetype_component_id: ArchetypeComponentId, - access: Rc>, + state: &'w WorldCellState, } -impl<'w, T> WorldBorrow<'w, T> { +impl<'w, T> WorldCellRes<'w, T> { fn new( value: &'w T, archetype_component_id: ArchetypeComponentId, - access: Rc>, + state: &'w WorldCellState, ) -> Self { - if !access.borrow_mut().read(archetype_component_id) { + if !state + .resource_access + .borrow_mut() + .read(archetype_component_id) + { panic!( "Attempted to immutably access {}, but it is already mutably borrowed", std::any::type_name::() @@ -99,12 +164,12 @@ impl<'w, T> WorldBorrow<'w, T> { Self { value, archetype_component_id, - access, + state, } } } -impl<'w, T> Deref for WorldBorrow<'w, T> { +impl<'w, T> Deref for WorldCellRes<'w, T> { type Target = T; #[inline] @@ -113,26 +178,30 @@ impl<'w, T> Deref for WorldBorrow<'w, T> { } } -impl<'w, T> Drop for WorldBorrow<'w, T> { +impl<'w, T> Drop for WorldCellRes<'w, T> { fn drop(&mut self) { - let mut access = self.access.borrow_mut(); + let mut access = self.state.resource_access.borrow_mut(); access.drop_read(self.archetype_component_id); } } -pub struct WorldBorrowMut<'w, T> { +pub struct WorldCellResMut<'w, T> { value: Mut<'w, T>, archetype_component_id: ArchetypeComponentId, - access: Rc>, + state: &'w WorldCellState, } -impl<'w, T> WorldBorrowMut<'w, T> { +impl<'w, T> WorldCellResMut<'w, T> { fn new( value: Mut<'w, T>, archetype_component_id: ArchetypeComponentId, - access: Rc>, + state: &'w WorldCellState, ) -> Self { - if !access.borrow_mut().write(archetype_component_id) { + if !state + .resource_access + .borrow_mut() + .write(archetype_component_id) + { panic!( "Attempted to mutably access {}, but it is already mutably borrowed", std::any::type_name::() @@ -141,12 +210,12 @@ impl<'w, T> WorldBorrowMut<'w, T> { Self { value, archetype_component_id, - access, + state, } } } -impl<'w, T> Deref for WorldBorrowMut<'w, T> { +impl<'w, T> Deref for WorldCellResMut<'w, T> { type Target = T; #[inline] @@ -155,92 +224,258 @@ impl<'w, T> Deref for WorldBorrowMut<'w, T> { } } -impl<'w, T> DerefMut for WorldBorrowMut<'w, T> { +impl<'w, T> DerefMut for WorldCellResMut<'w, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.value } } -impl<'w, T> Drop for WorldBorrowMut<'w, T> { +impl<'w, T> Drop for WorldCellResMut<'w, T> { fn drop(&mut self) { - let mut access = self.access.borrow_mut(); + let mut access = self.state.resource_access.borrow_mut(); access.drop_write(self.archetype_component_id); } } impl<'w> WorldCell<'w> { pub(crate) fn new(world: &'w mut World) -> Self { - // this is cheap because ArchetypeComponentAccess::new() is const / allocation free - let access = std::mem::replace( - &mut world.archetype_component_access, - ArchetypeComponentAccess::new(), - ); - // world's ArchetypeComponentAccess is recycled to cut down on allocations - Self { - world, - access: Rc::new(RefCell::new(access)), - } + // this is cheap because WorldCellState::new() is const / allocation free + let state = std::mem::replace(&mut world.world_cell_state, WorldCellState::new()); + // world's WorldCellState is recycled to cut down on allocations + Self { world, state } } - pub fn get_resource(&self) -> Option> { + pub fn get_resource(&self) -> Option> { let component_id = self.world.components.get_resource_id(TypeId::of::())?; let resource_archetype = self.world.archetypes.resource(); let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldBorrow::new( + Some(WorldCellRes::new( // SAFE: ComponentId matches TypeId unsafe { self.world.get_resource_with_id(component_id)? }, archetype_component_id, - self.access.clone(), + &self.state, )) } - pub fn get_resource_mut(&self) -> Option> { + pub fn get_resource_mut(&self) -> Option> { let component_id = self.world.components.get_resource_id(TypeId::of::())?; let resource_archetype = self.world.archetypes.resource(); let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldBorrowMut::new( - // SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut + Some(WorldCellResMut::new( + // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut unsafe { self.world .get_resource_unchecked_mut_with_id(component_id)? }, archetype_component_id, - self.access.clone(), + &self.state, )) } - pub fn get_non_send(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = self.world.archetypes.resource(); + pub fn get_non_send(&self) -> Option> { + let world = &self.world; + let component_id = world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = world.archetypes.resource(); let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldBorrow::new( + Some(WorldCellRes::new( // SAFE: ComponentId matches TypeId - unsafe { self.world.get_non_send_with_id(component_id)? }, + unsafe { world.get_non_send_with_id(component_id)? }, archetype_component_id, - self.access.clone(), + &self.state, )) } - pub fn get_non_send_mut(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = self.world.archetypes.resource(); + pub fn get_non_send_mut(&self) -> Option> { + let world = &self.world; + let component_id = world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = world.archetypes.resource(); let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldBorrowMut::new( - // SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut - unsafe { - self.world - .get_non_send_unchecked_mut_with_id(component_id)? - }, + Some(WorldCellResMut::new( + // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut + unsafe { world.get_non_send_unchecked_mut_with_id(component_id)? }, archetype_component_id, - self.access.clone(), + &self.state, )) } + + pub fn init_query(&mut self) -> QueryToken { + self.init_filtered_query() + } + + pub fn init_filtered_query(&mut self) -> QueryToken + where + Q: WorldQuery + 'static, + F: WorldQuery + 'static, + F::Fetch: FilterFetch, + { + let key = TypeId::of::>(); + let world = &mut self.world; + self.state.query_cache.entry(key).or_insert_with(|| { + Rc::new(QueryCacheEntry { + alive_count: Cell::new(0), + query: world.query_filtered::(), + }) + }); + QueryToken(PhantomData) + } + + /// Requires `init_query` with the right type to be called beforehand + pub fn query(&self, token: QueryToken) -> WorldCellQuery + where + Q: WorldQuery + 'static, + F: WorldQuery + 'static, + F::Fetch: FilterFetch, + { + // token is only used to statically pass the query initialization state + let _ = token; + let key = TypeId::of::>(); + let state = self + .state + .query_cache + .get(&key) + .expect("token cannot exist without initialization"); + // self.world.query() + WorldCellQuery { + query_entry: state.clone(), + state: &self.state, + world: self.world, + marker: PhantomData, + } + } +} + +#[derive(Clone, Copy)] +pub struct QueryToken(PhantomData<(Q, F)>) +where + Q: WorldQuery + 'static, + F: WorldQuery + 'static, + F::Fetch: FilterFetch; + +pub struct WorldCellQuery<'w, Q, F> { + query_entry: Rc, + state: &'w WorldCellState, + world: &'w World, + marker: PhantomData<(Q, F)>, +} + +impl<'w, Q, F> WorldCellQuery<'w, Q, F> +where + Q: WorldQuery + 'static, + F: WorldQuery + 'static, + F::Fetch: FilterFetch, +{ + #[allow(dead_code)] + fn iter(&self) -> WorldCellIter> { + let query = self + .query_entry + .query + .as_any() + .downcast_ref::>() + .unwrap(); + // cast away the query_entry lifetime, so we can return an iterator that's self-referential + // SAFETY: + // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter + let query = unsafe { (query as *const QueryState).as_ref().unwrap() }; + + // TODO: assert correct access + assert_component_access_compatibility( + std::any::type_name::(), + std::any::type_name::(), + &query.component_access, + self.world, + self.state, + ); + + let iter = unsafe { + query.iter_unchecked_manual( + self.world, + self.world.last_change_tick(), + self.world.read_change_tick(), + ) + }; + WorldCellIter::new(self.query_entry.clone(), iter) + } +} + +fn assert_component_access_compatibility( + query_type: &'static str, + filter_type: &'static str, + current: &FilteredAccess, + world: &World, + state: &WorldCellState, +) { + let mut conflicts = state.get_live_query_conflicts_filtered(current); + if conflicts.is_empty() { + return; + } + let conflicting_components = conflicts + .drain(..) + .map(|component_id| world.components.get_info(component_id).unwrap().name()) + .collect::>(); + let accesses = conflicting_components.join(", "); + panic!("Query<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", + query_type, filter_type, accesses); +} + +pub struct WorldCellIter { + inner: I, + // Rc holds data referenced in `inner`. Must be dropped last. + // That Rc is normally held inside `WorldCellState` anyway, but holding it directly allows to guarantee + // safety easier, as `WorldCellState` is now free to evict cache at any time without consequences + query_entry: Rc, +} + +impl Drop for WorldCellIter { + fn drop(&mut self) { + self.query_entry + .alive_count + .set(self.query_entry.alive_count.get() - 1); + } +} + +impl<'w, I> WorldCellIter { + fn new( + query_entry: Rc, + inner: I, + // state: Rc, + ) -> Self { + query_entry + .alive_count + .set(query_entry.alive_count.get() + 1); + + Self { query_entry, inner } + } +} + +impl Iterator for WorldCellIter { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for WorldCellIter { + fn len(&self) -> usize { + self.inner.len() + } } #[cfg(test)] mod tests { + use super::BASE_ACCESS; - use crate::{archetype::ArchetypeId, world::World}; + use crate::{ + self as bevy_ecs, + archetype::ArchetypeId, + component::Component, + prelude::Without, + world::{QueryToken, World, WorldCell}, + }; use std::any::TypeId; #[test] @@ -300,10 +535,15 @@ mod tests { let u32_archetype_component_id = resource_archetype .get_archetype_component_id(u32_component_id) .unwrap(); - assert_eq!(world.archetype_component_access.access.len(), 1); + assert_eq!( + world.world_cell_state.resource_access.borrow().access.len(), + 1 + ); assert_eq!( world - .archetype_component_access + .world_cell_state + .resource_access + .borrow() .access .get(u32_archetype_component_id), Some(&BASE_ACCESS), @@ -350,4 +590,163 @@ mod tests { let _value_a = cell.get_resource_mut::().unwrap(); let _value_b = cell.get_resource::().unwrap(); } + + #[derive(Component, Debug, Clone, PartialEq)] + struct A; + #[derive(Component, Debug, Clone, PartialEq)] + struct B; + + #[test] + fn world_cell_query() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + let t2 = cell.init_query::<&mut B>(); + let t3 = cell.init_filtered_query::<&mut B, Without>(); + let t4 = cell.init_query::<(&mut A, &mut B)>(); + + let q1 = cell.query(t1); + let q2 = cell.query(t2); + let q3 = cell.query(t3); + let q4 = cell.query(t4); + + let mut vals = Vec::new(); + for x in q1.iter() { + for y in q2.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, B), (A, B), (A, B), (A, B)]); + + let mut vals = Vec::new(); + for x in q2.iter() { + for y in q1.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(B, A), (B, A), (B, A), (B, A)]); + + let mut vals = Vec::new(); + for x in q3.iter() { + for (y1, y2) in q4.iter() { + vals.push((x.clone(), y1.clone(), y2.clone())); + } + } + assert_eq!(vals, vec![(B, A, B)]); + } + + #[test] + #[should_panic] + fn world_cell_query_access_panic() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + let t2 = cell.init_query::<(&A, &mut B)>(); + + let q1 = cell.query(t1); + let q2 = cell.query(t2); + + for _x in q1.iter() { + for _y in q2.iter() { + // should panic + } + } + } + + #[test] + fn world_cell_query_twice() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&A>(); + + let q1 = cell.query(t1); + + let mut vals = Vec::new(); + for x in q1.iter() { + for y in q1.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]); + } + + #[test] + #[should_panic] + fn world_cell_query_twice_mut() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + + let q1 = cell.query(t1); + + for _x in q1.iter() { + for _y in q1.iter() { + // should panic + } + } + } + + #[test] + fn world_cell_query_in_fn() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_filtered_query(); + let t2 = cell.init_filtered_query(); + let t3 = cell.init_filtered_query(); + + perform_query_a(&cell, t1); + perform_query_b(&cell, t2, t3); + + fn perform_query_a(world: &WorldCell, t: QueryToken<&A>) { + let mut vals = Vec::new(); + let q = world.query(t); + for x in q.iter() { + for y in q.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]) + } + + fn perform_query_b( + world: &WorldCell, + t1: QueryToken<(&mut A, &mut B)>, + t2: QueryToken<&mut B, Without>, + ) { + let mut vals = Vec::new(); + let q1 = world.query(t1); + let q2 = world.query(t2); + for (x1, x2) in q1.iter() { + for y in q2.iter() { + vals.push((x1.clone(), x2.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, B, B)]) + } + } } From 08c8ebbb7426bc398f82b1b18a7d4d5815f1d0d3 Mon Sep 17 00:00:00 2001 From: Frizi Date: Sun, 25 Jul 2021 15:17:35 +0200 Subject: [PATCH 2/5] add entrity commands to WorldCell --- crates/bevy_ecs/src/world/world_cell.rs | 240 ++++++++++++++++++------ 1 file changed, 187 insertions(+), 53 deletions(-) diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index d2103db734d7b..85a9b1ba14398 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -1,9 +1,10 @@ use crate::{ archetype::ArchetypeComponentId, - component::ComponentId, + component::{Component, ComponentId}, + prelude::{Bundle, Entity}, query::{FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, storage::SparseSet, - system::Resource, + system::{CommandQueue, Despawn, Insert, InsertBundle, Remove, RemoveBundle, Resource}, world::{Mut, World}, }; use std::{ @@ -24,6 +25,7 @@ pub struct WorldCell<'w> { struct QueryCacheEntry { alive_count: Cell, + in_working_set: Cell, query: Q, } @@ -56,8 +58,10 @@ where pub(crate) struct WorldCellState { resource_access: RefCell, - // component_access: RefCell, query_cache: HashMap, fxhash::FxBuildHasher>, + /// Queries that were activated at least once in the current WorldCell session. + query_cache_working_set: RefCell>>, + command_queue: RefCell, } impl WorldCellState { @@ -68,6 +72,8 @@ impl WorldCellState { resource_access: RefCell::new(ArchetypeComponentAccess::new()), // component_access: RefCell::new(ComponentAccess::new()), query_cache: HashMap::default(), + query_cache_working_set: Default::default(), + command_queue: Default::default(), } } @@ -75,7 +81,7 @@ impl WorldCellState { &self, filtered_access: &FilteredAccess, ) -> Vec { - for query in self.query_cache.values() { + for query in self.query_cache_working_set.borrow().iter() { if let Some(current_filtered_access) = query.alive_filtered_access() { if !current_filtered_access.is_compatible(filtered_access) { return current_filtered_access @@ -134,6 +140,8 @@ impl ArchetypeComponentAccess { impl<'w> Drop for WorldCell<'w> { fn drop(&mut self) { + self.maintain(); + // give world ArchetypeComponentAccess back to reuse allocations let _ = std::mem::swap(&mut self.world.world_cell_state, &mut self.state); } @@ -245,6 +253,26 @@ impl<'w> WorldCell<'w> { Self { world, state } } + pub fn spawn(&self) -> CellEntityCommands<'_> { + self.entity(self.world.entities.reserve_entity()) + } + + pub fn entity(&self, entity: Entity) -> CellEntityCommands<'_> { + CellEntityCommands { + entity, + state: &self.state, + } + } + + /// A WorldCell session "barrier". Applies world commands issued thus far, optimizing future query accesses. + pub fn maintain(&mut self) { + // Clear working set when the WorldCell session ends. + for entry in self.state.query_cache_working_set.get_mut().drain(..) { + entry.in_working_set.set(false); + } + self.state.command_queue.borrow_mut().apply(self.world); + } + pub fn get_resource(&self) -> Option> { let component_id = self.world.components.get_resource_id(TypeId::of::())?; let resource_archetype = self.world.archetypes.resource(); @@ -313,14 +341,16 @@ impl<'w> WorldCell<'w> { self.state.query_cache.entry(key).or_insert_with(|| { Rc::new(QueryCacheEntry { alive_count: Cell::new(0), + in_working_set: Cell::new(false), query: world.query_filtered::(), }) }); + QueryToken(PhantomData) } /// Requires `init_query` with the right type to be called beforehand - pub fn query(&self, token: QueryToken) -> WorldCellQuery + pub fn query(&self, token: QueryToken) -> CellQuery where Q: WorldQuery + 'static, F: WorldQuery + 'static, @@ -328,15 +358,26 @@ impl<'w> WorldCell<'w> { { // token is only used to statically pass the query initialization state let _ = token; + let key = TypeId::of::>(); - let state = self + let query_entry = self .state .query_cache .get(&key) .expect("token cannot exist without initialization"); - // self.world.query() - WorldCellQuery { - query_entry: state.clone(), + + // the token existence guarantees that the query was initialized, but not necessarily in the same WorldCell session. + // So instead of during initialization, we add queries to working set at the first use in each session. + if !query_entry.in_working_set.get() { + query_entry.in_working_set.set(true); + self.state + .query_cache_working_set + .borrow_mut() + .push(query_entry.clone()); + } + + CellQuery { + query_entry: query_entry.clone(), state: &self.state, world: self.world, marker: PhantomData, @@ -344,6 +385,76 @@ impl<'w> WorldCell<'w> { } } +/// A list of commands that will be run to modify an [`Entity`] inside `WorldCell`. +pub struct CellEntityCommands<'a> { + entity: Entity, + state: &'a WorldCellState, +} + +impl<'a> CellEntityCommands<'a> { + /// Retrieves the current entity's unique [`Entity`] id. + #[inline] + pub fn id(&self) -> Entity { + self.entity + } + + /// Adds a [`Bundle`] of components to the current entity. + pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { + self.state.command_queue.borrow_mut().push(InsertBundle { + entity: self.entity, + bundle, + }); + self + } + + /// Adds a single [`Component`] to the current entity. + /// + /// `Self::insert` can be chained with [`WorldCell::spawn`]. + /// + /// See [`Commands::insert`] for analogous method in [`Commands`]. + pub fn insert(&mut self, component: impl Component) -> &mut Self { + self.state.command_queue.borrow_mut().push(Insert { + entity: self.entity, + component, + }); + self + } + + /// See [`EntityMut::remove_bundle`](crate::world::EntityMut::remove_bundle). + pub fn remove_bundle(&mut self) -> &mut Self + where + T: Bundle, + { + self.state + .command_queue + .borrow_mut() + .push(RemoveBundle:: { + entity: self.entity, + phantom: PhantomData, + }); + self + } + + /// See [`EntityMut::remove`](crate::world::EntityMut::remove). + pub fn remove(&mut self) -> &mut Self + where + T: Component, + { + self.state.command_queue.borrow_mut().push(Remove:: { + entity: self.entity, + phantom: PhantomData, + }); + self + } + + /// Despawns only the specified entity, not including its children. + pub fn despawn(&mut self) { + self.state.command_queue.borrow_mut().push(Despawn { + entity: self.entity, + }) + } +} + #[derive(Clone, Copy)] pub struct QueryToken(PhantomData<(Q, F)>) where @@ -351,49 +462,22 @@ where F: WorldQuery + 'static, F::Fetch: FilterFetch; -pub struct WorldCellQuery<'w, Q, F> { +pub struct CellQuery<'w, Q, F> { query_entry: Rc, state: &'w WorldCellState, world: &'w World, marker: PhantomData<(Q, F)>, } -impl<'w, Q, F> WorldCellQuery<'w, Q, F> +impl<'w, Q, F> CellQuery<'w, Q, F> where Q: WorldQuery + 'static, F: WorldQuery + 'static, F::Fetch: FilterFetch, { #[allow(dead_code)] - fn iter(&self) -> WorldCellIter> { - let query = self - .query_entry - .query - .as_any() - .downcast_ref::>() - .unwrap(); - // cast away the query_entry lifetime, so we can return an iterator that's self-referential - // SAFETY: - // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter - let query = unsafe { (query as *const QueryState).as_ref().unwrap() }; - - // TODO: assert correct access - assert_component_access_compatibility( - std::any::type_name::(), - std::any::type_name::(), - &query.component_access, - self.world, - self.state, - ); - - let iter = unsafe { - query.iter_unchecked_manual( - self.world, - self.world.last_change_tick(), - self.world.read_change_tick(), - ) - }; - WorldCellIter::new(self.query_entry.clone(), iter) + fn iter(&self) -> CellQueryIter<'w, '_, Q, F> { + CellQueryIter::new(self) } } @@ -413,19 +497,29 @@ fn assert_component_access_compatibility( .map(|component_id| world.components.get_info(component_id).unwrap().name()) .collect::>(); let accesses = conflicting_components.join(", "); - panic!("Query<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", + panic!("CellQuery<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", query_type, filter_type, accesses); } -pub struct WorldCellIter { - inner: I, +pub struct CellQueryIter<'w, 's, Q, F> +where + Q: WorldQuery, + F: WorldQuery, + F::Fetch: FilterFetch, +{ + inner: QueryIter<'w, 's, Q, F>, // Rc holds data referenced in `inner`. Must be dropped last. // That Rc is normally held inside `WorldCellState` anyway, but holding it directly allows to guarantee // safety easier, as `WorldCellState` is now free to evict cache at any time without consequences query_entry: Rc, } -impl Drop for WorldCellIter { +impl<'w, 's, Q, F> Drop for CellQueryIter<'w, 's, Q, F> +where + Q: WorldQuery, + F: WorldQuery, + F::Fetch: FilterFetch, +{ fn drop(&mut self) { self.query_entry .alive_count @@ -433,12 +527,41 @@ impl Drop for WorldCellIter { } } -impl<'w, I> WorldCellIter { - fn new( - query_entry: Rc, - inner: I, - // state: Rc, - ) -> Self { +impl<'w, 's, Q, F> CellQueryIter<'w, 's, Q, F> +where + Q: WorldQuery + 'static, + F: WorldQuery + 'static, + F::Fetch: FilterFetch, +{ + fn new(cell_query: &'s CellQuery<'w, Q, F>) -> Self { + let query = cell_query + .query_entry + .query + .as_any() + .downcast_ref::>() + .unwrap(); + // cast away the query_entry lifetime, so we can return an iterator that's self-referential + // SAFETY: + // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter + let query = unsafe { (query as *const QueryState).as_ref().unwrap() }; + + assert_component_access_compatibility( + std::any::type_name::(), + std::any::type_name::(), + &query.component_access, + cell_query.world, + cell_query.state, + ); + + let inner = unsafe { + query.iter_unchecked_manual( + cell_query.world, + cell_query.world.last_change_tick(), + cell_query.world.read_change_tick(), + ) + }; + + let query_entry = cell_query.query_entry.clone(); query_entry .alive_count .set(query_entry.alive_count.get() + 1); @@ -447,8 +570,13 @@ impl<'w, I> WorldCellIter { } } -impl Iterator for WorldCellIter { - type Item = I::Item; +impl<'w, 's, Q, F> Iterator for CellQueryIter<'w, 's, Q, F> +where + Q: WorldQuery, + F: WorldQuery, + F::Fetch: FilterFetch, +{ + type Item = as Iterator>::Item; fn next(&mut self) -> Option { self.inner.next() @@ -459,7 +587,13 @@ impl Iterator for WorldCellIter { } } -impl ExactSizeIterator for WorldCellIter { +impl<'w, 's, Q, F> ExactSizeIterator for CellQueryIter<'w, 's, Q, F> +where + Q: WorldQuery, + F: WorldQuery, + F::Fetch: FilterFetch, + QueryIter<'w, 's, Q, F>: ExactSizeIterator, +{ fn len(&self) -> usize { self.inner.len() } From 7edd431d0265ec610e5bd0e98793b0c36875de6f Mon Sep 17 00:00:00 2001 From: Frizi Date: Sun, 26 Sep 2021 20:58:46 +0200 Subject: [PATCH 3/5] immediately access modified components in WorldCell queries --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/query/access.rs | 8 + crates/bevy_ecs/src/world/append_list.rs | 457 ++++++++ crates/bevy_ecs/src/world/mod.rs | 1 + crates/bevy_ecs/src/world/world_cell.rs | 1002 ++++------------- .../bevy_ecs/src/world/world_cell/command.rs | 114 ++ crates/bevy_ecs/src/world/world_cell/query.rs | 520 +++++++++ .../src/world/world_cell/query/fetch.rs | 450 ++++++++ .../bevy_ecs/src/world/world_cell/resource.rs | 327 ++++++ 9 files changed, 2094 insertions(+), 786 deletions(-) create mode 100644 crates/bevy_ecs/src/world/append_list.rs create mode 100644 crates/bevy_ecs/src/world/world_cell/command.rs create mode 100644 crates/bevy_ecs/src/world/world_cell/query.rs create mode 100644 crates/bevy_ecs/src/world/world_cell/query/fetch.rs create mode 100644 crates/bevy_ecs/src/world/world_cell/resource.rs diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 7332197560a70..d7b57e98549de 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -18,6 +18,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", optional = true } bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" } bevy_utils = { path = "../bevy_utils", version = "0.5.0" } bevy_ecs_macros = { path = "macros", version = "0.5.0" } +bumpalo = "3.8" async-channel = "1.4" fixedbitset = "0.4" diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index f735f36580f32..a6c8acd26ffaf 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -140,6 +140,14 @@ impl FilteredAccess { &self.access } + pub(crate) fn with(&self) -> &FixedBitSet { + &self.with + } + + pub(crate) fn without(&self) -> &FixedBitSet { + &self.without + } + pub fn add_read(&mut self, index: T) { self.access.add_read(index.clone()); self.add_with(index); diff --git a/crates/bevy_ecs/src/world/append_list.rs b/crates/bevy_ecs/src/world/append_list.rs new file mode 100644 index 0000000000000..a7eeca9e15ef6 --- /dev/null +++ b/crates/bevy_ecs/src/world/append_list.rs @@ -0,0 +1,457 @@ +//! A copy of "appendlist" crate sources, extended with draining iterator. + +// MIT License + +// Copyright (c) 2019 Daniel Dulaney + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use std::cell::{Cell, UnsafeCell}; +use std::fmt::{self, Debug}; +use std::iter::FromIterator; +use std::ops::Index; + +// Must be a power of 2 +const FIRST_CHUNK_SIZE: usize = 16; + +pub const fn chunk_size(chunk_id: usize) -> usize { + // First chunk is FIRST_CHUNK_SIZE, subsequent chunks double each time + FIRST_CHUNK_SIZE << chunk_id +} + +pub const fn chunk_start(chunk_id: usize) -> usize { + // This looks like magic, but I promise it works + // Essentially, each chunk is the size of the sum of all chunks before + // it. Except that the first chunk is different: it "should" be preceded + // by a whole list of chunks that sum to its size, but it's not. Therefore, + // there's a "missing" set of chunks the size of the first chunk, so + // later chunks need to be updated. + chunk_size(chunk_id) - FIRST_CHUNK_SIZE +} + +pub const fn index_chunk(index: usize) -> usize { + // This *is* magic + floor_log2(index + FIRST_CHUNK_SIZE) - floor_log2(FIRST_CHUNK_SIZE) +} + +#[inline] +pub const fn floor_log2(x: usize) -> usize { + const BITS_PER_BYTE: usize = 8; + + BITS_PER_BYTE * std::mem::size_of::() - (x.leading_zeros() as usize) - 1 +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn chunk_sizes_make_sense() { + assert_eq!(chunk_size(0), FIRST_CHUNK_SIZE); + + let mut index = 0; + + for chunk in 0..20 { + // Each chunk starts just after the previous one ends + assert_eq!(chunk_start(chunk), index); + index += chunk_size(chunk); + } + } + + #[test] + fn index_chunk_matches_up() { + for index in 0..1_000_000 { + let chunk_id = index_chunk(index); + + // Each index happens after its chunk start and before its chunk end + assert!(index >= chunk_start(chunk_id)); + assert!(index < chunk_start(chunk_id) + chunk_size(chunk_id)); + } + } +} + +/// A list that can be appended to while elements are borrowed +/// +/// This looks like a fairly bare-bones list API, except that it has a `push` +/// method that works on non-`mut` lists. It is safe to hold references to +/// values inside this list and push a new value onto the end. +/// +/// Additionally, the list has O(1) index and O(1) push (not amortized!). +/// +/// # Implementation details +/// +/// This section is not necessary to use the API, it just describes the underlying +/// allocation and indexing strategies. +/// +/// The list is a `Vec` of *chunks*. Each chunk is itself a `Vec`. The list +/// will fill up a chunk, then allocate a new chunk with its full capacity. +/// Because the capacity of a given chunk never changes, the underlying `Vec` +/// never reallocates, so references to that chunk are never invalidated. Each +/// chunk is twice the size of the previous chunk, so there will never be more +/// than O(log(n)) chunks. +/// +/// Constant-time indexing is achieved because the chunk ID of a particular index +/// can be quickly calculated: if the first chunk has size c, index i will be +/// located in chunk floor(log2(i + c) - log2(c)). If c is a power of 2, this +/// is equivalent to floor(log2(i + c)) - floor(log2(c)), and a very fast floor +/// log2 algorithm can be derived from `usize::leading_zeros()`. +pub struct AppendList { + chunks: UnsafeCell>>, + len: Cell, +} + +impl AppendList { + /// Wrapper to get the list of chunks immutably + fn chunks(&self) -> &[Vec] { + unsafe { &*self.chunks.get() } + } + + /// In test builds, check all of the unsafe invariants + /// + /// In release builds, no-op + fn check_invariants(&self) { + #[cfg(test)] + { + if self.len.get() > 0 { + // Correct number of chunks + assert_eq!(index_chunk(self.len.get() - 1), self.chunks().len() - 1); + + // Every chunk holds enough items + for chunk_id in 0..self.chunks().len() { + assert!(chunk_size(chunk_id) <= self.chunks()[chunk_id].capacity()); + } + + // Intermediate chunks are full + for chunk_id in 0..self.chunks().len() - 1 { + assert_eq!(chunk_size(chunk_id), self.chunks()[chunk_id].len()); + } + + // Last chunk is correct length + assert_eq!( + self.chunks().last().unwrap().len(), + self.len.get() - chunk_start(self.chunks().len() - 1) + ); + } else { + // No chunks + assert_eq!(0, self.chunks().len()); + } + } + } + + /// Create a new `AppendList` + pub fn new() -> Self { + Self { + chunks: UnsafeCell::new(Vec::new()), + len: Cell::new(0), + } + } + + /// Append an item to the end + /// + /// Note that this does not require `mut`. + pub fn push(&self, item: T) { + self.check_invariants(); + + // Unsafe code alert! + // + // Preserve the following invariants: + // - Only the last chunk may be modified + // - A chunk cannot ever be reallocated + // - len must reflect the length + // + // Invariants are checked in the check_invariants method + let mut_chunks = unsafe { &mut *self.chunks.get() }; + + let new_index = self.len.get(); + let chunk_id = index_chunk(new_index); + + if chunk_id < mut_chunks.len() { + // We should always be inserting into the last chunk + debug_assert_eq!(chunk_id, mut_chunks.len() - 1); + + // Insert into the appropriate chunk + let chunk = &mut mut_chunks[chunk_id]; + + // The chunk must not be reallocated! Save the pre-insertion capacity + // so we can check it later (debug builds only) + #[cfg(test)] + let prev_capacity = chunk.capacity(); + + // Do the insertion + chunk.push(item); + + // Check that the capacity didn't change (debug builds only) + #[cfg(test)] + assert_eq!(prev_capacity, chunk.capacity()); + } else { + // Need to allocate a new chunk + + // New chunk should be the immediate next chunk + debug_assert_eq!(chunk_id, mut_chunks.len()); + + // New chunk must be big enough + let mut new_chunk = Vec::with_capacity(chunk_size(chunk_id)); + debug_assert!(new_chunk.capacity() >= chunk_size(chunk_id)); + + new_chunk.push(item); + + mut_chunks.push(new_chunk); + } + + // Increment the length + self.len.set(self.len.get() + 1); + + self.check_invariants(); + } + + /// Get the length of the list + pub fn len(&self) -> usize { + self.check_invariants(); + + self.len.get() + } + + /// Get an item from the list, if it is in bounds + /// + /// Returns `None` if the `index` is out-of-bounds. Note that you can also + /// index with `[]`, which will panic on out-of-bounds. + pub fn get(&self, index: usize) -> Option<&T> { + self.check_invariants(); + + if index >= self.len() { + return None; + } + + let chunk_id = index_chunk(index); + let chunk_start = chunk_start(chunk_id); + + return Some(&self.chunks()[chunk_id][index - chunk_start]); + } + + /// Get an iterator over the list + pub fn iter(&self) -> Iter { + self.check_invariants(); + + Iter { + list: &self, + index: 0, + } + } + + pub fn drain(&mut self) -> impl Iterator + '_ { + self.chunks.get_mut().drain(..).flatten() + } +} + +impl Default for AppendList { + fn default() -> Self { + Self::new() + } +} + +impl Index for AppendList { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index) + .expect("AppendList indexed beyond its length") + } +} + +impl FromIterator for AppendList { + fn from_iter>(iter: I) -> Self { + let list = Self::new(); + + for item in iter { + list.push(item); + } + + list + } +} + +impl<'l, T> IntoIterator for &'l AppendList { + type Item = &'l T; + type IntoIter = Iter<'l, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl PartialEq for AppendList { + fn eq(&self, other: &AppendList) -> bool { + let mut s = self.iter(); + let mut o = other.iter(); + + loop { + match (s.next(), o.next()) { + (Some(a), Some(b)) if a == b => {} + (None, None) => return true, + _ => return false, + } + } + } +} + +impl Debug for AppendList { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_list().entries(self.iter()).finish() + } +} + +pub struct Iter<'l, T> { + list: &'l AppendList, + index: usize, +} + +impl<'l, T> Iterator for Iter<'l, T> { + type Item = &'l T; + + fn next(&mut self) -> Option { + let item = self.list.get(self.index); + + self.index += 1; + + item + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.list.len() - self.index; + + (remaining, Some(remaining)) + } +} + +#[cfg(test)] +mod test_list { + use super::*; + + #[test] + fn from_iterator() { + let l: AppendList = (0..100).collect(); + + for i in 0..100 { + assert_eq!(l[i], i as i32); + } + } + + #[test] + fn iterator() { + let l: AppendList = (0..100).collect(); + let mut i1 = l.iter(); + let mut i2 = l.into_iter(); + + for item in 0..100 { + assert_eq!(i1.next(), Some(&item)); + assert_eq!(i2.next(), Some(&item)); + } + + assert_eq!(i1.next(), None); + assert_eq!(i2.next(), None); + } + + #[test] + fn equality() { + let a = AppendList::new(); + let b = AppendList::new(); + + assert_eq!(a, b); + + a.push("foo"); + + assert_ne!(a, b); + + b.push("foo"); + + assert_eq!(a, b); + + a.push("bar"); + a.push("baz"); + + assert_ne!(a, b); + } + + #[test] + fn iterator_size_hint() { + let l: AppendList = AppendList::new(); + let mut i = l.iter(); + assert_eq!(i.size_hint(), (0, Some(0))); + + l.push(1); + assert_eq!(i.size_hint(), (1, Some(1))); + + l.push(2); + assert_eq!(i.size_hint(), (2, Some(2))); + + i.next(); + assert_eq!(i.size_hint(), (1, Some(1))); + + l.push(3); + assert_eq!(i.size_hint(), (2, Some(2))); + + i.next(); + assert_eq!(i.size_hint(), (1, Some(1))); + + i.next(); + assert_eq!(i.size_hint(), (0, Some(0))); + } + + #[test] + fn empty_list() { + let n: AppendList = AppendList::new(); + + assert_eq!(n.len(), 0); + assert_eq!(n.get(0), None); + + let d: AppendList = AppendList::default(); + + assert_eq!(d.len(), 0); + assert_eq!(d.get(0), None); + } + + #[test] + fn thousand_item_list() { + test_big_list(1_000); + } + + #[test] + #[ignore] + fn million_item_list() { + test_big_list(1_000_000); + } + + fn test_big_list(size: usize) { + let l = AppendList::new(); + let mut refs = Vec::new(); + + for i in 0..size { + assert_eq!(l.len(), i); + + l.push(i); + refs.push(l[i]); + + assert_eq!(l.len(), i + 1); + } + + for i in 0..size { + assert_eq!(Some(&refs[i]), l.get(i)); + } + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index cb99e314373d6..e9e42d8f17737 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,3 +1,4 @@ +mod append_list; mod entity_ref; mod spawn_batch; mod world_cell; diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 85a9b1ba14398..fc394bf29790d 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -1,21 +1,28 @@ +mod command; +mod query; +mod resource; + +use bevy_utils::HashSet; + use crate::{ - archetype::ArchetypeComponentId, component::{Component, ComponentId}, - prelude::{Bundle, Entity}, - query::{FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, - storage::SparseSet, - system::{CommandQueue, Despawn, Insert, InsertBundle, Remove, RemoveBundle, Resource}, - world::{Mut, World}, + prelude::Entity, + query::FilteredAccess, + system::{Command, Despawn, Remove}, + world::{append_list::AppendList, world_cell::command::CellInsert, World}, }; use std::{ - any::{Any, TypeId}, - cell::{Cell, RefCell}, - collections::HashMap, - marker::PhantomData, - ops::{Deref, DerefMut}, + any::TypeId, + cell::RefCell, + collections::{hash_map::Entry::Occupied, HashMap}, rc::Rc, }; +use self::command::CellEntityCommands; +pub use self::query::{CellQuery, QueryToken}; +use self::query::{FetchRefs, QueryCacheEntry}; +use self::resource::ArchetypeComponentAccess; + /// Exposes safe mutable access to multiple resources at a time in a World. Attempting to access /// World in a way that violates Rust's mutability rules will panic thanks to runtime checks. pub struct WorldCell<'w> { @@ -23,45 +30,13 @@ pub struct WorldCell<'w> { pub(crate) state: WorldCellState, } -struct QueryCacheEntry { - alive_count: Cell, - in_working_set: Cell, - query: Q, -} - -impl QueryCacheEntry { - fn alive_filtered_access(&self) -> Option<&FilteredAccess> { - if self.alive_count.get() > 0 { - Some(self.query.component_access()) - } else { - None - } - } -} - -trait DynQueryState: Any { - fn component_access(&self) -> &FilteredAccess; - fn as_any(&self) -> &dyn Any; -} - -impl DynQueryState for QueryState -where - F::Fetch: FilterFetch, -{ - fn component_access(&self) -> &FilteredAccess { - &self.component_access - } - fn as_any(&self) -> &dyn Any { - self - } -} - pub(crate) struct WorldCellState { resource_access: RefCell, query_cache: HashMap, fxhash::FxBuildHasher>, /// Queries that were activated at least once in the current WorldCell session. query_cache_working_set: RefCell>>, - command_queue: RefCell, + command_queue: CellCommandQueue, + current_query_refs: FetchRefs, } impl WorldCellState { @@ -74,6 +49,7 @@ impl WorldCellState { query_cache: HashMap::default(), query_cache_working_set: Default::default(), command_queue: Default::default(), + current_query_refs: Default::default(), } } @@ -94,793 +70,247 @@ impl WorldCellState { } } -pub(crate) struct ArchetypeComponentAccess { - access: SparseSet, -} - -const UNIQUE_ACCESS: u32 = 0; -const BASE_ACCESS: u32 = 1; -impl ArchetypeComponentAccess { - const fn new() -> Self { - Self { - access: SparseSet::new(), - } - } - - fn read(&mut self, id: ArchetypeComponentId) -> bool { - let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); - if *id_access == UNIQUE_ACCESS { - false - } else { - *id_access += 1; - true - } - } - - fn drop_read(&mut self, id: ArchetypeComponentId) { - let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); - *id_access -= 1; - } - - fn write(&mut self, id: ArchetypeComponentId) -> bool { - let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); - if *id_access == BASE_ACCESS { - *id_access = UNIQUE_ACCESS; - true - } else { - false - } - } - - fn drop_write(&mut self, id: ArchetypeComponentId) { - let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); - *id_access = BASE_ACCESS; - } +// how to merge real result with overlay? +// how to handle inserts that results in query visiting new element? +// first: prepare set of types that influence query +// - how to handle Without? Only deletions (how?) and inserts (filter out) matter +// - how to handle With? only deletions (filter out) and inserts (how?) matter +// +// create a temp world that only contains the affected entities as clones? +// create a structure that describes the "diff" internal structure as a pass-through API + +#[derive(Default)] +pub struct WorldOverlay { + touched_entities: HashSet, + inserted: HashMap>, + removed: HashMap>, + despawned_entities: HashSet, } -impl<'w> Drop for WorldCell<'w> { - fn drop(&mut self) { - self.maintain(); - - // give world ArchetypeComponentAccess back to reuse allocations - let _ = std::mem::swap(&mut self.world.world_cell_state, &mut self.state); - } -} - -pub struct WorldCellRes<'w, T> { - value: &'w T, - archetype_component_id: ArchetypeComponentId, - state: &'w WorldCellState, +pub trait CellCommand: Command { + fn apply_overlay( + &self, + self_index: usize, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ); } -impl<'w, T> WorldCellRes<'w, T> { - fn new( - value: &'w T, - archetype_component_id: ArchetypeComponentId, - state: &'w WorldCellState, - ) -> Self { - if !state - .resource_access - .borrow_mut() - .read(archetype_component_id) - { - panic!( - "Attempted to immutably access {}, but it is already mutably borrowed", - std::any::type_name::() - ) - } - Self { - value, - archetype_component_id, - state, +impl CellCommand for CellInsert { + fn apply_overlay( + &self, + self_index: usize, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ) { + if let Some(id) = world.components().get_id(TypeId::of::()) { + if access.with().contains(id.index()) + || access.without().contains(id.index()) + || access.access().has_read(id) + || access.access().has_write(id) + { + overlay.touched_entities.insert(self.entity); + match overlay.removed.entry(self.entity) { + Occupied(mut entry) => { + let v = entry.get_mut(); + v.retain(|c_id| *c_id != id); + if v.is_empty() { + entry.remove(); + } + } + _ => {} + } + overlay + .inserted + .entry(self.entity) + .and_modify(|v| match v.iter_mut().find(|(c_id, _)| *c_id == id) { + Some((_, overlay)) => *overlay = self_index, + None => v.push((id, self_index)), + }) + .or_insert_with(|| vec![(id, self_index)]); + } } } } -impl<'w, T> Deref for WorldCellRes<'w, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - self.value - } -} - -impl<'w, T> Drop for WorldCellRes<'w, T> { - fn drop(&mut self) { - let mut access = self.state.resource_access.borrow_mut(); - access.drop_read(self.archetype_component_id); - } -} - -pub struct WorldCellResMut<'w, T> { - value: Mut<'w, T>, - archetype_component_id: ArchetypeComponentId, - state: &'w WorldCellState, -} - -impl<'w, T> WorldCellResMut<'w, T> { - fn new( - value: Mut<'w, T>, - archetype_component_id: ArchetypeComponentId, - state: &'w WorldCellState, - ) -> Self { - if !state - .resource_access - .borrow_mut() - .write(archetype_component_id) - { - panic!( - "Attempted to mutably access {}, but it is already mutably borrowed", - std::any::type_name::() - ) - } - Self { - value, - archetype_component_id, - state, +impl CellCommand for Remove { + fn apply_overlay( + &self, + _self_index: usize, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ) { + if let Some(id) = world.components().get_id(TypeId::of::()) { + if access.with().contains(id.index()) + || access.without().contains(id.index()) + || access.access().has_read(id) + || access.access().has_write(id) + { + overlay.touched_entities.insert(self.entity); + match overlay.inserted.entry(self.entity) { + Occupied(mut entry) => { + let v = entry.get_mut(); + v.retain(|(c_id, _)| *c_id != id); + if v.is_empty() { + entry.remove(); + } + } + _ => {} + } + overlay.removed.entry(self.entity).and_modify(|v| { + if !v.contains(&id) { + v.push(id); + } + }); + } } } } -impl<'w, T> Deref for WorldCellResMut<'w, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - self.value.deref() +impl CellCommand for Despawn { + fn apply_overlay( + &self, + _self_index: usize, + overlay: &mut WorldOverlay, + _world: &World, + _access: &FilteredAccess, + ) { + overlay.touched_entities.insert(self.entity); + overlay.despawned_entities.insert(self.entity); } } -impl<'w, T> DerefMut for WorldCellResMut<'w, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.value - } +struct CellCommandMeta { + ptr: *mut u8, + write: unsafe fn(value: *mut u8, world: &mut World), + apply_overlay: unsafe fn( + value: *const u8, + self_index: usize, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ), } -impl<'w, T> Drop for WorldCellResMut<'w, T> { - fn drop(&mut self) { - let mut access = self.state.resource_access.borrow_mut(); - access.drop_write(self.archetype_component_id); - } +/// A queue of [`CellCommand`]s +// +// NOTE: See [`CommandQueue`] as an analog for normal commands. +#[derive(Default)] +pub struct CellCommandQueue { + bump: bumpalo::Bump, + metas: AppendList, } -impl<'w> WorldCell<'w> { - pub(crate) fn new(world: &'w mut World) -> Self { - // this is cheap because WorldCellState::new() is const / allocation free - let state = std::mem::replace(&mut world.world_cell_state, WorldCellState::new()); - // world's WorldCellState is recycled to cut down on allocations - Self { world, state } - } - - pub fn spawn(&self) -> CellEntityCommands<'_> { - self.entity(self.world.entities.reserve_entity()) - } - - pub fn entity(&self, entity: Entity) -> CellEntityCommands<'_> { - CellEntityCommands { - entity, - state: &self.state, - } - } - - /// A WorldCell session "barrier". Applies world commands issued thus far, optimizing future query accesses. - pub fn maintain(&mut self) { - // Clear working set when the WorldCell session ends. - for entry in self.state.query_cache_working_set.get_mut().drain(..) { - entry.in_working_set.set(false); - } - self.state.command_queue.borrow_mut().apply(self.world); - } - - pub fn get_resource(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = self.world.archetypes.resource(); - let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldCellRes::new( - // SAFE: ComponentId matches TypeId - unsafe { self.world.get_resource_with_id(component_id)? }, - archetype_component_id, - &self.state, - )) - } - - pub fn get_resource_mut(&self) -> Option> { - let component_id = self.world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = self.world.archetypes.resource(); - let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldCellResMut::new( - // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut - unsafe { - self.world - .get_resource_unchecked_mut_with_id(component_id)? - }, - archetype_component_id, - &self.state, - )) - } - - pub fn get_non_send(&self) -> Option> { - let world = &self.world; - let component_id = world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = world.archetypes.resource(); - let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldCellRes::new( - // SAFE: ComponentId matches TypeId - unsafe { world.get_non_send_with_id(component_id)? }, - archetype_component_id, - &self.state, - )) - } +// SAFE: All commands [`Command`] implement [`Send`] +unsafe impl Send for CellCommandQueue {} - pub fn get_non_send_mut(&self) -> Option> { - let world = &self.world; - let component_id = world.components.get_resource_id(TypeId::of::())?; - let resource_archetype = world.archetypes.resource(); - let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; - Some(WorldCellResMut::new( - // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut - unsafe { world.get_non_send_unchecked_mut_with_id(component_id)? }, - archetype_component_id, - &self.state, - )) - } - - pub fn init_query(&mut self) -> QueryToken { - self.init_filtered_query() - } +// SAFE: `&CommandQueue` never gives access to the inner commands. +unsafe impl Sync for CellCommandQueue {} - pub fn init_filtered_query(&mut self) -> QueryToken - where - Q: WorldQuery + 'static, - F: WorldQuery + 'static, - F::Fetch: FilterFetch, - { - let key = TypeId::of::>(); - let world = &mut self.world; - self.state.query_cache.entry(key).or_insert_with(|| { - Rc::new(QueryCacheEntry { - alive_count: Cell::new(0), - in_working_set: Cell::new(false), - query: world.query_filtered::(), - }) - }); - - QueryToken(PhantomData) - } - - /// Requires `init_query` with the right type to be called beforehand - pub fn query(&self, token: QueryToken) -> CellQuery - where - Q: WorldQuery + 'static, - F: WorldQuery + 'static, - F::Fetch: FilterFetch, - { - // token is only used to statically pass the query initialization state - let _ = token; - - let key = TypeId::of::>(); - let query_entry = self - .state - .query_cache - .get(&key) - .expect("token cannot exist without initialization"); - - // the token existence guarantees that the query was initialized, but not necessarily in the same WorldCell session. - // So instead of during initialization, we add queries to working set at the first use in each session. - if !query_entry.in_working_set.get() { - query_entry.in_working_set.set(true); - self.state - .query_cache_working_set - .borrow_mut() - .push(query_entry.clone()); - } - - CellQuery { - query_entry: query_entry.clone(), - state: &self.state, - world: self.world, - marker: PhantomData, - } - } -} - -/// A list of commands that will be run to modify an [`Entity`] inside `WorldCell`. -pub struct CellEntityCommands<'a> { - entity: Entity, - state: &'a WorldCellState, -} - -impl<'a> CellEntityCommands<'a> { - /// Retrieves the current entity's unique [`Entity`] id. +impl CellCommandQueue { + /// Push a [`Command`] onto the queue. #[inline] - pub fn id(&self) -> Entity { - self.entity - } - - /// Adds a [`Bundle`] of components to the current entity. - pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { - self.state.command_queue.borrow_mut().push(InsertBundle { - entity: self.entity, - bundle, - }); - self - } - - /// Adds a single [`Component`] to the current entity. - /// - /// `Self::insert` can be chained with [`WorldCell::spawn`]. - /// - /// See [`Commands::insert`] for analogous method in [`Commands`]. - pub fn insert(&mut self, component: impl Component) -> &mut Self { - self.state.command_queue.borrow_mut().push(Insert { - entity: self.entity, - component, - }); - self - } - - /// See [`EntityMut::remove_bundle`](crate::world::EntityMut::remove_bundle). - pub fn remove_bundle(&mut self) -> &mut Self - where - T: Bundle, - { - self.state - .command_queue - .borrow_mut() - .push(RemoveBundle:: { - entity: self.entity, - phantom: PhantomData, - }); - self - } - - /// See [`EntityMut::remove`](crate::world::EntityMut::remove). - pub fn remove(&mut self) -> &mut Self + pub fn push(&self, command: C) where - T: Component, + C: CellCommand, { - self.state.command_queue.borrow_mut().push(Remove:: { - entity: self.entity, - phantom: PhantomData, - }); - self - } - - /// Despawns only the specified entity, not including its children. - pub fn despawn(&mut self) { - self.state.command_queue.borrow_mut().push(Despawn { - entity: self.entity, - }) - } -} - -#[derive(Clone, Copy)] -pub struct QueryToken(PhantomData<(Q, F)>) -where - Q: WorldQuery + 'static, - F: WorldQuery + 'static, - F::Fetch: FilterFetch; - -pub struct CellQuery<'w, Q, F> { - query_entry: Rc, - state: &'w WorldCellState, - world: &'w World, - marker: PhantomData<(Q, F)>, -} - -impl<'w, Q, F> CellQuery<'w, Q, F> -where - Q: WorldQuery + 'static, - F: WorldQuery + 'static, - F::Fetch: FilterFetch, -{ - #[allow(dead_code)] - fn iter(&self) -> CellQueryIter<'w, '_, Q, F> { - CellQueryIter::new(self) - } -} - -fn assert_component_access_compatibility( - query_type: &'static str, - filter_type: &'static str, - current: &FilteredAccess, - world: &World, - state: &WorldCellState, -) { - let mut conflicts = state.get_live_query_conflicts_filtered(current); - if conflicts.is_empty() { - return; - } - let conflicting_components = conflicts - .drain(..) - .map(|component_id| world.components.get_info(component_id).unwrap().name()) - .collect::>(); - let accesses = conflicting_components.join(", "); - panic!("CellQuery<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", - query_type, filter_type, accesses); -} - -pub struct CellQueryIter<'w, 's, Q, F> -where - Q: WorldQuery, - F: WorldQuery, - F::Fetch: FilterFetch, -{ - inner: QueryIter<'w, 's, Q, F>, - // Rc holds data referenced in `inner`. Must be dropped last. - // That Rc is normally held inside `WorldCellState` anyway, but holding it directly allows to guarantee - // safety easier, as `WorldCellState` is now free to evict cache at any time without consequences - query_entry: Rc, -} - -impl<'w, 's, Q, F> Drop for CellQueryIter<'w, 's, Q, F> -where - Q: WorldQuery, - F: WorldQuery, - F::Fetch: FilterFetch, -{ - fn drop(&mut self) { - self.query_entry - .alive_count - .set(self.query_entry.alive_count.get() - 1); - } -} - -impl<'w, 's, Q, F> CellQueryIter<'w, 's, Q, F> -where - Q: WorldQuery + 'static, - F: WorldQuery + 'static, - F::Fetch: FilterFetch, -{ - fn new(cell_query: &'s CellQuery<'w, Q, F>) -> Self { - let query = cell_query - .query_entry - .query - .as_any() - .downcast_ref::>() - .unwrap(); - // cast away the query_entry lifetime, so we can return an iterator that's self-referential - // SAFETY: - // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter - let query = unsafe { (query as *const QueryState).as_ref().unwrap() }; - - assert_component_access_compatibility( - std::any::type_name::(), - std::any::type_name::(), - &query.component_access, - cell_query.world, - cell_query.state, - ); - - let inner = unsafe { - query.iter_unchecked_manual( - cell_query.world, - cell_query.world.last_change_tick(), - cell_query.world.read_change_tick(), - ) - }; - - let query_entry = cell_query.query_entry.clone(); - query_entry - .alive_count - .set(query_entry.alive_count.get() + 1); - - Self { query_entry, inner } - } -} - -impl<'w, 's, Q, F> Iterator for CellQueryIter<'w, 's, Q, F> -where - Q: WorldQuery, - F: WorldQuery, - F::Fetch: FilterFetch, -{ - type Item = as Iterator>::Item; - - fn next(&mut self) -> Option { - self.inner.next() - } - - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} - -impl<'w, 's, Q, F> ExactSizeIterator for CellQueryIter<'w, 's, Q, F> -where - Q: WorldQuery, - F: WorldQuery, - F::Fetch: FilterFetch, - QueryIter<'w, 's, Q, F>: ExactSizeIterator, -{ - fn len(&self) -> usize { - self.inner.len() - } -} - -#[cfg(test)] -mod tests { - - use super::BASE_ACCESS; - use crate::{ - self as bevy_ecs, - archetype::ArchetypeId, - component::Component, - prelude::Without, - world::{QueryToken, World, WorldCell}, - }; - use std::any::TypeId; - - #[test] - fn world_cell() { - let mut world = World::default(); - world.insert_resource(1u32); - world.insert_resource(1u64); - let cell = world.cell(); - { - let mut a = cell.get_resource_mut::().unwrap(); - assert_eq!(1, *a); - *a = 2; - } - { - let a = cell.get_resource::().unwrap(); - assert_eq!(2, *a, "ensure access is dropped"); - - let b = cell.get_resource::().unwrap(); - assert_eq!( - 2, *b, - "ensure multiple immutable accesses can occur at the same time" - ); + /// SAFE: This function is only every called when the `command` bytes is the associated + /// [`Commands`] `T` type. Also this only reads the data via `read_unaligned` so unaligned + /// accesses are safe. + unsafe fn write_command(command: *mut u8, world: &mut World) { + let command = command.cast::().read_unaligned(); + command.write(world); } - { - let a = cell.get_resource_mut::().unwrap(); - assert_eq!( - 2, *a, - "ensure both immutable accesses are dropped, enabling a new mutable access" - ); - - let b = cell.get_resource::().unwrap(); - assert_eq!( - 1, *b, - "ensure multiple non-conflicting mutable accesses can occur at the same time" - ); - } - } - #[test] - fn world_access_reused() { - let mut world = World::default(); - world.insert_resource(1u32); - { - let cell = world.cell(); - { - let mut a = cell.get_resource_mut::().unwrap(); - assert_eq!(1, *a); - *a = 2; - } + /// SAFE: This function is only every called when the `command` bytes is the associated + /// [`Commands`] `T` type. Also this only reads the data via `read_unaligned` so unaligned + /// accesses are safe. + unsafe fn apply_overlay_command( + command: *const u8, + self_index: usize, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ) { + let command = command.cast::().as_ref().unwrap(); + command.apply_overlay(self_index, overlay, world, access); } - let u32_component_id = world - .components - .get_resource_id(TypeId::of::()) - .unwrap(); - let resource_archetype = world.archetypes.get(ArchetypeId::RESOURCE).unwrap(); - let u32_archetype_component_id = resource_archetype - .get_archetype_component_id(u32_component_id) - .unwrap(); - assert_eq!( - world.world_cell_state.resource_access.borrow().access.len(), - 1 - ); - assert_eq!( - world - .world_cell_state - .resource_access - .borrow() - .access - .get(u32_archetype_component_id), - Some(&BASE_ACCESS), - "reused access count is 'base'" - ); - } - - #[test] - #[should_panic] - fn world_cell_double_mut() { - let mut world = World::default(); - world.insert_resource(1u32); - let cell = world.cell(); - let _value_a = cell.get_resource_mut::().unwrap(); - let _value_b = cell.get_resource_mut::().unwrap(); - } - - #[test] - #[should_panic] - fn world_cell_ref_and_mut() { - let mut world = World::default(); - world.insert_resource(1u32); - let cell = world.cell(); - let _value_a = cell.get_resource::().unwrap(); - let _value_b = cell.get_resource_mut::().unwrap(); - } - - #[test] - #[should_panic] - fn world_cell_mut_and_ref() { - let mut world = World::default(); - world.insert_resource(1u32); - let cell = world.cell(); - let _value_a = cell.get_resource_mut::().unwrap(); - let _value_b = cell.get_resource::().unwrap(); + let command = self.bump.alloc(command); + self.metas.push(CellCommandMeta { + ptr: command as *mut C as *mut u8, + write: write_command::, + apply_overlay: apply_overlay_command::, + }); } - #[test] - #[should_panic] - fn world_cell_ref_and_ref() { - let mut world = World::default(); - world.insert_resource(1u32); - let cell = world.cell(); - let _value_a = cell.get_resource_mut::().unwrap(); - let _value_b = cell.get_resource::().unwrap(); + /// SAFETY: must know that nth command is of type C + pub(crate) unsafe fn get_nth(&self, index: usize) -> &C { + let meta = &self.metas[index]; + meta.ptr.cast::().as_ref().unwrap() } - #[derive(Component, Debug, Clone, PartialEq)] - struct A; - #[derive(Component, Debug, Clone, PartialEq)] - struct B; - - #[test] - fn world_cell_query() { - let mut world = World::default(); - - world.spawn().insert_bundle((A, B)); - world.spawn().insert(A); - world.spawn().insert(B); - let mut cell = world.cell(); - - let t1 = cell.init_query::<&mut A>(); - let t2 = cell.init_query::<&mut B>(); - let t3 = cell.init_filtered_query::<&mut B, Without>(); - let t4 = cell.init_query::<(&mut A, &mut B)>(); - - let q1 = cell.query(t1); - let q2 = cell.query(t2); - let q3 = cell.query(t3); - let q4 = cell.query(t4); - - let mut vals = Vec::new(); - for x in q1.iter() { - for y in q2.iter() { - vals.push((x.clone(), y.clone())); - } - } - assert_eq!(vals, vec![(A, B), (A, B), (A, B), (A, B)]); + /// Execute the queued [`Command`]s in the world. + /// This clears the queue. + #[inline] + pub fn apply(&mut self, world: &mut World) { + // flush the previously queued entities + world.flush(); - let mut vals = Vec::new(); - for x in q2.iter() { - for y in q1.iter() { - vals.push((x.clone(), y.clone())); - } - } - assert_eq!(vals, vec![(B, A), (B, A), (B, A), (B, A)]); + // SAFE: In the iteration below, `meta.func` will safely consume and drop each pushed command. + // This operation is so that we can reuse the bytes `Vec`'s internal storage and prevent + // unnecessary allocations. - let mut vals = Vec::new(); - for x in q3.iter() { - for (y1, y2) in q4.iter() { - vals.push((x.clone(), y1.clone(), y2.clone())); + for meta in self.metas.drain() { + // SAFE: The implementation of `write_command` is safe for the according Command type. + // The bytes are safely cast to their original type, safely read, and then dropped. + unsafe { + (meta.write)(meta.ptr, world); } } - assert_eq!(vals, vec![(B, A, B)]); + self.bump.reset(); } - #[test] - #[should_panic] - fn world_cell_query_access_panic() { - let mut world = World::default(); - - world.spawn().insert_bundle((A, B)); - world.spawn().insert(A); - world.spawn().insert(B); - let mut cell = world.cell(); - - let t1 = cell.init_query::<&mut A>(); - let t2 = cell.init_query::<(&A, &mut B)>(); - - let q1 = cell.query(t1); - let q2 = cell.query(t2); - - for _x in q1.iter() { - for _y in q2.iter() { - // should panic + /// Execute the queued [`Command`]s in the world. + /// This clears the queue. + #[inline] + pub fn apply_overlay( + &self, + overlay: &mut WorldOverlay, + world: &World, + access: &FilteredAccess, + ) { + for (index, meta) in self.metas.iter().enumerate() { + // SAFE: The implementation of `apply_overlay_command` is safe for the according Command type. + // The bytes are safely cast to their original type and safely dereferenced. + unsafe { + (meta.apply_overlay)(meta.ptr, index, overlay, world, access); } } } +} - #[test] - fn world_cell_query_twice() { - let mut world = World::default(); - - world.spawn().insert_bundle((A, B)); - world.spawn().insert(A); - world.spawn().insert(B); - let mut cell = world.cell(); - - let t1 = cell.init_query::<&A>(); - - let q1 = cell.query(t1); +impl<'w> Drop for WorldCell<'w> { + fn drop(&mut self) { + self.maintain(); - let mut vals = Vec::new(); - for x in q1.iter() { - for y in q1.iter() { - vals.push((x.clone(), y.clone())); - } - } - assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]); + // give world WorldCellState back to reuse allocations + let _ = std::mem::swap(&mut self.world.world_cell_state, &mut self.state); } +} - #[test] - #[should_panic] - fn world_cell_query_twice_mut() { - let mut world = World::default(); - - world.spawn().insert_bundle((A, B)); - world.spawn().insert(A); - world.spawn().insert(B); - let mut cell = world.cell(); - - let t1 = cell.init_query::<&mut A>(); - - let q1 = cell.query(t1); - - for _x in q1.iter() { - for _y in q1.iter() { - // should panic - } - } +impl<'w> WorldCell<'w> { + pub(crate) fn new(world: &'w mut World) -> Self { + // this is cheap because WorldCellState::new() is const / allocation free + let state = std::mem::replace(&mut world.world_cell_state, WorldCellState::new()); + // world's WorldCellState is recycled to cut down on allocations + Self { world, state } } - #[test] - fn world_cell_query_in_fn() { - let mut world = World::default(); - - world.spawn().insert_bundle((A, B)); - world.spawn().insert(A); - world.spawn().insert(B); - let mut cell = world.cell(); - - let t1 = cell.init_filtered_query(); - let t2 = cell.init_filtered_query(); - let t3 = cell.init_filtered_query(); - - perform_query_a(&cell, t1); - perform_query_b(&cell, t2, t3); - - fn perform_query_a(world: &WorldCell, t: QueryToken<&A>) { - let mut vals = Vec::new(); - let q = world.query(t); - for x in q.iter() { - for y in q.iter() { - vals.push((x.clone(), y.clone())); - } - } - assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]) - } - - fn perform_query_b( - world: &WorldCell, - t1: QueryToken<(&mut A, &mut B)>, - t2: QueryToken<&mut B, Without>, - ) { - let mut vals = Vec::new(); - let q1 = world.query(t1); - let q2 = world.query(t2); - for (x1, x2) in q1.iter() { - for y in q2.iter() { - vals.push((x1.clone(), x2.clone(), y.clone())); - } - } - assert_eq!(vals, vec![(A, B, B)]) - } + pub fn spawn(&self) -> CellEntityCommands<'_> { + self.entity(self.world.entities.reserve_entity()) } } diff --git a/crates/bevy_ecs/src/world/world_cell/command.rs b/crates/bevy_ecs/src/world/world_cell/command.rs new file mode 100644 index 0000000000000..5f4ceda6b2aa9 --- /dev/null +++ b/crates/bevy_ecs/src/world/world_cell/command.rs @@ -0,0 +1,114 @@ +use crate::{ + component::Component, + prelude::{Entity, World}, + system::{Command, Despawn, Remove}, + world::{WorldCell, WorldCellState}, +}; +use std::{marker::PhantomData, sync::RwLock}; + +#[derive(Debug)] +pub struct CellInsert { + pub entity: Entity, + // this could be a RefCell, because we will use it from single thread anyway. + // Unfortunately, Command trait requires Sync. + pub component: RwLock, +} + +impl Command for CellInsert +where + T: Component, +{ + fn write(self, world: &mut World) { + world + .entity_mut(self.entity) + .insert::(self.component.into_inner().unwrap()); + } +} + +/// A list of commands that will be run to modify an [`Entity`] inside `WorldCell`. +pub struct CellEntityCommands<'a> { + entity: Entity, + state: &'a WorldCellState, +} + +impl<'a> CellEntityCommands<'a> { + /// Retrieves the current entity's unique [`Entity`] id. + #[inline] + pub fn id(&self) -> Entity { + self.entity + } + + // /// Adds a [`Bundle`] of components to the current entity. + // pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { + // self.state.command_queue.borrow_mut().push(InsertBundle { + // entity: self.entity, + // bundle, + // }); + // self + // } + + /// Adds a single [`Component`] to the current entity. + /// + /// `Self::insert` can be chained with [`WorldCell::spawn`]. + /// + /// See [`Commands::insert`] for analogous method in [`Commands`]. + pub fn insert(&mut self, component: impl Component) -> &mut Self { + self.state.command_queue.push(CellInsert { + entity: self.entity, + component: RwLock::new(component), + }); + self + } + + // /// See [`EntityMut::remove_bundle`](crate::world::EntityMut::remove_bundle). + // pub fn remove_bundle(&mut self) -> &mut Self + // where + // T: Bundle, + // { + // self.state + // .command_queue + // .borrow_mut() + // .push(RemoveBundle:: { + // entity: self.entity, + // phantom: PhantomData, + // }); + // self + // } + + /// See [`EntityMut::remove`](crate::world::EntityMut::remove). + pub fn remove(&mut self) -> &mut Self + where + T: Component, + { + self.state.command_queue.push(Remove:: { + entity: self.entity, + phantom: PhantomData, + }); + self + } + + /// Despawns only the specified entity, not including its children. + pub fn despawn(&mut self) { + self.state.command_queue.push(Despawn { + entity: self.entity, + }) + } +} + +impl<'w> WorldCell<'w> { + pub fn entity(&self, entity: Entity) -> CellEntityCommands<'_> { + CellEntityCommands { + entity, + state: &self.state, + } + } + + /// A WorldCell session "barrier". Applies world commands issued thus far, optimizing future query accesses. + pub fn maintain(&mut self) { + // Clear working set when the WorldCell session ends. + for entry in self.state.query_cache_working_set.get_mut().drain(..) { + entry.in_working_set.set(false); + } + self.state.command_queue.apply(self.world); + } +} diff --git a/crates/bevy_ecs/src/world/world_cell/query.rs b/crates/bevy_ecs/src/world/world_cell/query.rs new file mode 100644 index 0000000000000..ab19f87798915 --- /dev/null +++ b/crates/bevy_ecs/src/world/world_cell/query.rs @@ -0,0 +1,520 @@ +mod fetch; + +use crate::{ + component::{ComponentId, Components}, + prelude::Entity, + query::{FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, + world::{CellCommandQueue, World, WorldCell, WorldCellState, WorldOverlay}, +}; +use std::{ + any::{Any, TypeId}, + cell::Cell, + marker::PhantomData, + rc::Rc, +}; + +use fetch::CellFetch; +pub(crate) use fetch::FetchRefs; +pub use fetch::WorldCellQuery; + +pub(super) struct QueryCacheEntry { + pub(super) alive_count: Cell, + pub(super) in_working_set: Cell, + pub(super) query: Q, +} + +impl QueryCacheEntry { + pub(super) fn alive_filtered_access(&self) -> Option<&FilteredAccess> { + if self.alive_count.get() > 0 { + Some(&self.query.component_access()) + } else { + None + } + } +} + +pub(super) trait DynQueryState: Any { + fn component_access(&self) -> &FilteredAccess; + fn as_any(&self) -> &dyn Any; +} + +impl DynQueryState for QueryState +where + F::Fetch: FilterFetch, +{ + fn component_access(&self) -> &FilteredAccess { + &self.component_access + } + fn as_any(&self) -> &dyn Any { + self + } +} + +#[derive(Clone, Copy)] +pub struct QueryToken(pub(super) PhantomData<(Q, F)>) +where + Q: WorldCellQuery + 'static, + F: WorldCellQuery + 'static, + F::Fetch: FilterFetch; + +pub struct CellQuery<'w, Q, F> { + query_entry: Rc, + state: &'w WorldCellState, + world: &'w World, + marker: PhantomData<(Q, F)>, +} + +impl<'w, Q, F> CellQuery<'w, Q, F> +where + Q: WorldCellQuery + 'static, + F: WorldCellQuery + 'static, + F::Fetch: FilterFetch, +{ + #[allow(dead_code)] + pub fn iter(&self) -> CellQueryIter<'w, '_, Q, F> { + CellQueryIter::new(self) + } +} + +fn assert_component_access_compatibility( + query_type: &'static str, + filter_type: &'static str, + current: &FilteredAccess, + world: &World, + state: &WorldCellState, +) { + let mut conflicts = state.get_live_query_conflicts_filtered(current); + if conflicts.is_empty() { + return; + } + let conflicting_components = conflicts + .drain(..) + .map(|component_id| world.components.get_info(component_id).unwrap().name()) + .collect::>(); + let accesses = conflicting_components.join(", "); + panic!("CellQuery<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", + query_type, filter_type, accesses); +} + +pub struct CellQueryIter<'w, 's, Q, F> +where + Q: WorldCellQuery, + F: WorldCellQuery, + F::Fetch: FilterFetch, +{ + inner: QueryIter<'w, 's, (Entity, Q), F>, + // Rc holds data referenced in `inner`. Must be dropped last. + // That Rc is normally held inside `WorldCellState` anyway, but holding it directly allows to guarantee + // safety easier, as `WorldCellState` is now free to evict cache at any time without consequences + // extra_ + query_entry: Rc, + refs: FetchRefs, + command_queue: &'w CellCommandQueue, + components: &'w Components, + overlay: WorldOverlay, +} + +impl<'w, 's, Q, F> Drop for CellQueryIter<'w, 's, Q, F> +where + Q: WorldCellQuery, + F: WorldCellQuery, + F::Fetch: FilterFetch, +{ + fn drop(&mut self) { + self.query_entry + .alive_count + .set(self.query_entry.alive_count.get() - 1); + } +} + +impl<'w, 's, Q, F> CellQueryIter<'w, 's, Q, F> +where + Q: WorldCellQuery + 'static, + F: WorldCellQuery + 'static, + F::Fetch: FilterFetch, +{ + fn new(cell_query: &'s CellQuery<'w, Q, F>) -> Self { + let query = cell_query + .query_entry + .query + .as_any() + .downcast_ref::>() + .unwrap(); + // cast away the query_entry lifetime, so we can return an iterator that's self-referential + // SAFETY: + // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter + let query = unsafe { + (query as *const QueryState<(Entity, Q), F>) + .as_ref() + .unwrap() + }; + + assert_component_access_compatibility( + std::any::type_name::(), + std::any::type_name::(), + &query.component_access, + &cell_query.world, + &cell_query.state, + ); + + let inner = unsafe { + query.iter_unchecked_manual( + cell_query.world, + cell_query.world.last_change_tick(), + cell_query.world.read_change_tick(), + ) + }; + + let query_entry = cell_query.query_entry.clone(); + query_entry + .alive_count + .set(query_entry.alive_count.get() + 1); + + let mut overlay = WorldOverlay::default(); + + // prepare filters and modifiers based on current commands + cell_query.state.command_queue.apply_overlay( + &mut overlay, + &cell_query.world, + &query.component_access, + ); + + Self { + query_entry, + inner, + refs: cell_query.state.current_query_refs.clone(), + command_queue: &cell_query.state.command_queue, + components: &cell_query.world.components, + overlay, + } + } +} + +impl<'w, 's, Q, F> Iterator for CellQueryIter<'w, 's, Q, F> +where + Q: WorldCellQuery, + F: WorldCellQuery, + F::Fetch: FilterFetch, +{ + type Item = >::CellItem; + + fn next(&mut self) -> Option { + loop { + let (entity, data) = self.inner.next()?; + // no processing necessary + if !self.overlay.touched_entities.contains(&entity) { + return Some(Q::CellFetch::wrap(data, entity, &self.refs)); + } + + if self.overlay.despawned_entities.contains(&entity) { + continue; + } + + // TODO: filter out with/without + match Q::CellFetch::overlay( + data, + entity, + &self.refs, + &self.overlay, + self.components, + &self.command_queue, + ) { + Some(data) => return Some(data), + None => continue, + } + } + + // TODO: handle extra matches + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +// not colliding queries: +// q1: <&mut A, Without> +// q2: <&mut A, With> +// q1, insert B, q2 + +impl<'w, 's, Q, F> ExactSizeIterator for CellQueryIter<'w, 's, Q, F> +where + Q: WorldCellQuery, + F: WorldCellQuery, + F::Fetch: FilterFetch, + QueryIter<'w, 's, (Entity, Q), F>: ExactSizeIterator, +{ + fn len(&self) -> usize { + self.inner.len() + } +} + +impl<'w> WorldCell<'w> { + pub fn init_query(&mut self) -> QueryToken { + self.init_filtered_query() + } + + pub fn init_filtered_query(&mut self) -> QueryToken + where + Q: WorldCellQuery + 'static, + F: WorldCellQuery + 'static, + F::Fetch: FilterFetch, + { + let key = TypeId::of::>(); + let world = &mut self.world; + self.state.query_cache.entry(key).or_insert_with(|| { + Rc::new(QueryCacheEntry { + alive_count: Cell::new(0), + in_working_set: Cell::new(false), + query: world.query_filtered::<(Entity, Q), F>(), + }) + }); + + QueryToken(PhantomData) + } + + /// Requires `init_query` with the right type to be called beforehand + pub fn query(&self, token: QueryToken) -> CellQuery + where + Q: WorldCellQuery + 'static, + F: WorldCellQuery + 'static, + F::Fetch: FilterFetch, + { + // token is only used to statically pass the query initialization state + let _ = token; + + let key = TypeId::of::>(); + let query_entry = self + .state + .query_cache + .get(&key) + .expect("token cannot exist without initialization"); + + // the token existence guarantees that the query was initialized, but not necessarily in the same WorldCell session. + // So instead of during initialization, we add queries to working set at the first use in each session. + if !query_entry.in_working_set.get() { + query_entry.in_working_set.set(true); + self.state + .query_cache_working_set + .borrow_mut() + .push(query_entry.clone()); + } + + CellQuery { + query_entry: query_entry.clone(), + state: &self.state, + world: &self.world, + marker: PhantomData, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + self as bevy_ecs, + prelude::{Component, Entity, Without}, + world::{QueryToken, World, WorldCell}, + }; + + #[derive(Component, Debug, Clone, PartialEq)] + struct A; + #[derive(Component, Debug, Clone, PartialEq)] + struct B; + + #[derive(Component, Debug, Clone, PartialEq)] + struct C(usize); + + #[test] + fn world_cell_query() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + let t2 = cell.init_query::<&mut B>(); + let t3 = cell.init_filtered_query::<&mut B, Without>(); + let t4 = cell.init_query::<(&mut A, &mut B)>(); + + let q1 = cell.query(t1); + let q2 = cell.query(t2); + let q3 = cell.query(t3); + let q4 = cell.query(t4); + + let mut vals = Vec::new(); + for x in q1.iter() { + for y in q2.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, B), (A, B), (A, B), (A, B)]); + + let mut vals = Vec::new(); + for x in q2.iter() { + for y in q1.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(B, A), (B, A), (B, A), (B, A)]); + + let mut vals = Vec::new(); + for x in q3.iter() { + for (y1, y2) in q4.iter() { + vals.push((x.clone(), y1.clone(), y2.clone())); + } + } + assert_eq!(vals, vec![(B, A, B)]); + } + + #[test] + #[should_panic] + fn world_cell_query_access_panic() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + let t2 = cell.init_query::<(&A, &mut B)>(); + + let q1 = cell.query(t1); + let q2 = cell.query(t2); + + for _x in q1.iter() { + for _y in q2.iter() { + // should panic + } + } + } + + #[test] + fn world_cell_query_twice() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&A>(); + + let q1 = cell.query(t1); + + let mut vals = Vec::new(); + for x in q1.iter() { + for y in q1.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]); + } + + #[test] + #[should_panic] + fn world_cell_query_twice_mut() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_query::<&mut A>(); + + let q1 = cell.query(t1); + + for _x in q1.iter() { + for _y in q1.iter() { + // should panic + } + } + } + + #[test] + fn world_cell_query_in_fn() { + let mut world = World::default(); + + world.spawn().insert_bundle((A, B)); + world.spawn().insert(A); + world.spawn().insert(B); + let mut cell = world.cell(); + + let t1 = cell.init_filtered_query(); + let t2 = cell.init_filtered_query(); + let t3 = cell.init_filtered_query(); + + perform_query_a(&cell, t1); + perform_query_b(&cell, t2, t3); + + fn perform_query_a(world: &WorldCell, t: QueryToken<&A>) { + let mut vals = Vec::new(); + let q = world.query(t); + for x in q.iter() { + for y in q.iter() { + vals.push((x.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, A), (A, A), (A, A), (A, A)]) + } + + fn perform_query_b( + world: &WorldCell, + t1: QueryToken<(&mut A, &mut B)>, + t2: QueryToken<&mut B, Without>, + ) { + let mut vals = Vec::new(); + let q1 = world.query(t1); + let q2 = world.query(t2); + for (x1, x2) in q1.iter() { + for y in q2.iter() { + vals.push((x1.clone(), x2.clone(), y.clone())); + } + } + assert_eq!(vals, vec![(A, B, B)]) + } + } + + #[test] + fn world_cell_query_overlay() { + let mut world = World::default(); + + world.spawn().insert(A).insert(C(0)); + world.spawn().insert(A).insert(C(1)); + world.spawn().insert(A).insert(C(2)); + // world.spawn() + let mut cell = world.cell(); + + let t1 = cell.init_query::<(Entity, &A)>(); + let t2 = cell.init_query::<(&A, &C)>(); + + let q1 = cell.query(t1); + let q2 = cell.query(t2); + + let mut vals = Vec::new(); + for (i, (entity, _)) in q1.iter().enumerate() { + cell.entity(entity).insert(C(10 + i)); + for (a, c) in q2.iter() { + vals.push((a.clone(), c.clone())); + } + } + assert_eq!( + vals, + vec![ + (A, C(10)), + (A, C(1)), + (A, C(2)), + (A, C(10)), + (A, C(11)), + (A, C(2)), + (A, C(10)), + (A, C(11)), + (A, C(12)) + ] + ); + } +} diff --git a/crates/bevy_ecs/src/world/world_cell/query/fetch.rs b/crates/bevy_ecs/src/world/world_cell/query/fetch.rs new file mode 100644 index 0000000000000..577875b937c11 --- /dev/null +++ b/crates/bevy_ecs/src/world/world_cell/query/fetch.rs @@ -0,0 +1,450 @@ +use std::{ + any::TypeId, + cell::RefCell, + rc::Rc, + sync::{RwLockReadGuard, RwLockWriteGuard}, +}; + +use bevy_utils::HashMap; + +use crate::{ + component::{Component, Components}, + prelude::{Entity, Mut}, + query::{EntityFetch, Fetch, ReadFetch, WithFetch, WithoutFetch, WorldQuery, WriteFetch}, + world::{world_cell::command::CellInsert, CellCommandQueue, WorldOverlay}, +}; + +pub(crate) type FetchRefs = Rc>; + +#[derive(Default)] +pub struct FetchAccess { + access: HashMap<(u32, TypeId), u32>, +} + +const UNIQUE_ACCESS: u32 = 0; +const BASE_ACCESS: u32 = 1; +impl FetchAccess { + fn get_or_base(&mut self, entity: Entity, id: TypeId) -> &mut u32 { + self.access.entry((entity.id(), id)).or_insert(BASE_ACCESS) + } + + fn read(&mut self, entity: Entity, id: TypeId) -> bool { + let id_access = self.get_or_base(entity, id); + if *id_access == UNIQUE_ACCESS { + false + } else { + *id_access += 1; + true + } + } + + fn drop_read(&mut self, entity: Entity, id: TypeId) { + let id_access = self.get_or_base(entity, id); + *id_access -= 1; + } + + fn write(&mut self, entity: Entity, id: TypeId) -> bool { + let id_access = self.get_or_base(entity, id); + if *id_access == BASE_ACCESS { + *id_access = UNIQUE_ACCESS; + true + } else { + false + } + } + + fn drop_write(&mut self, entity: Entity, id: TypeId) { + let id_access = self.get_or_base(entity, id); + *id_access = BASE_ACCESS; + } +} + +pub enum CellRef<'w, T: 'static> { + World { + inner: &'w T, + entity: Entity, + refs: FetchRefs, + }, + Overlay { + guard: RwLockReadGuard<'w, T>, + }, +} + +impl<'w, T: 'static> CellRef<'w, T> { + fn new(inner: &'w T, entity: Entity, refs: &FetchRefs) -> Self { + if !refs.borrow_mut().read(entity, TypeId::of::()) { + panic!( + "Component '{}', of entity {:?} already mutably borrowed", + std::any::type_name::(), + entity + ); + } + Self::World { + inner, + entity, + refs: refs.clone(), + } + } + + fn new_overlay(guard: RwLockReadGuard<'w, T>) -> Self { + Self::Overlay { guard } + } +} + +impl<'w, T> std::ops::Deref for CellRef<'w, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + Self::World { inner, .. } => inner, + Self::Overlay { guard } => guard.deref(), + } + } +} + +impl<'w, T: 'static> Drop for CellRef<'w, T> { + fn drop(&mut self) { + match self { + Self::World { entity, refs, .. } => { + refs.borrow_mut().drop_read(*entity, TypeId::of::()) + } + Self::Overlay { .. } => {} + } + } +} + +pub enum CellMut<'w, T: 'static> { + World { + inner: Mut<'w, T>, + entity: Entity, + refs: FetchRefs, + }, + Overlay { + guard: RwLockWriteGuard<'w, T>, + }, +} + +impl<'w, T: 'static> CellMut<'w, T> { + fn new(inner: Mut<'w, T>, entity: Entity, refs: &FetchRefs) -> Self { + if !refs.borrow_mut().write(entity, TypeId::of::()) { + panic!( + "Component '{}' of entity {:?} already borrowed", + std::any::type_name::(), + entity + ); + } + Self::World { + inner, + entity, + refs: refs.clone(), + } + } + + fn new_overlay(guard: RwLockWriteGuard<'w, T>) -> Self { + Self::Overlay { guard } + } +} + +impl<'w, T> std::ops::Deref for CellMut<'w, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + Self::World { inner, .. } => inner.deref(), + Self::Overlay { guard } => guard.deref(), + } + } +} + +impl<'w, T> std::ops::DerefMut for CellMut<'w, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::World { inner, .. } => inner.deref_mut(), + Self::Overlay { guard } => guard.deref_mut(), + } + } +} + +impl<'w, T: 'static> Drop for CellMut<'w, T> { + fn drop(&mut self) { + match self { + Self::World { entity, refs, .. } => { + refs.borrow_mut().drop_write(*entity, TypeId::of::()) + } + Self::Overlay { .. } => {} + } + } +} + +pub trait WorldCellQuery: WorldQuery { + type CellFetch: for<'world, 'state> CellFetch< + 'world, + 'state, + State = Self::State, + Item = >::Item, + >; +} + +impl WorldCellQuery for T +where + T: WorldQuery, + T::Fetch: for<'world, 'state> CellFetch<'world, 'state>, +{ + type CellFetch = T::Fetch; +} + +// pub trait CellFilterFetch: FilterFetch + for<'w> CellFetch<'w> {} +// impl CellFilterFetch for T where T: FilterFetch + for<'w> CellFetch<'w> {} + +pub trait CellFetch<'world, 'state>: Fetch<'world, 'state> { + type CellItem; + // just wrap original data without further filtering + fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem; + + // wrap original data, perform filtering and replacements using overlay data + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, + ) -> Option; + + // fetch using combined world and overlay data + fn fetch_overlay( + &mut self, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + ) -> Option; +} + +impl<'world, 'state> CellFetch<'world, 'state> for EntityFetch { + type CellItem = >::Item; + fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { + inner + } + + fn overlay( + inner: Self::Item, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + _command_queue: &'world CellCommandQueue, + ) -> Option { + Some(inner) + } + + #[inline] + fn fetch_overlay( + &mut self, + entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + Some(entity) + } +} + +impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithFetch { + type CellItem = >::Item; + fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { + inner + } + + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + _command_queue: &'world CellCommandQueue, + ) -> Option { + if let Some(removed) = overlay.removed.get(&entity) { + let id = components.get_id(TypeId::of::())?; + if removed.contains(&id) { + return None; + } + } + Some(Self::wrap(inner, entity, refs)) + } + + fn fetch_overlay( + &mut self, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + todo!() + } +} + +impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithoutFetch { + type CellItem = >::Item; + fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { + inner + } + + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + _command_queue: &'world CellCommandQueue, + ) -> Option { + if let Some(inserted) = overlay.inserted.get(&entity) { + let id = components.get_id(TypeId::of::())?; + if inserted.iter().find(|i| i.0 == id).is_some() { + return None; + } + } + Some(Self::wrap(inner, entity, refs)) + } + + fn fetch_overlay( + &mut self, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + todo!() + } +} + +impl<'world, 'state, T: Component> CellFetch<'world, 'state> for ReadFetch { + type CellItem = CellRef<'world, T>; + fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem { + CellRef::new(inner, entity, refs) + } + + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, + ) -> Option { + let id = components.get_id(TypeId::of::())?; + // component removed, filter the result out + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return None; + } + } + // component inserted, return a reference to inserted component + if let Some(inserted) = overlay.inserted.get(&entity) { + if let Some((_, cmd_id)) = inserted.iter().find(|i| i.0 == id) { + let cmd = unsafe { command_queue.get_nth::>(*cmd_id) }; + let guard = cmd.component.try_read().expect("already borrowed"); + return Some(CellRef::new_overlay(guard)); + } + } + + Some(Self::wrap(inner, entity, refs)) + } + + fn fetch_overlay( + &mut self, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + todo!() + } +} + +impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WriteFetch { + type CellItem = CellMut<'world, T>; + fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem { + CellMut::new(inner, entity, refs) + } + + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, + ) -> Option { + let id = components.get_id(TypeId::of::())?; + // component removed, filter the result out + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return None; + } + } + // component inserted, return a reference to inserted component + if let Some(inserted) = overlay.inserted.get(&entity) { + if let Some((_, cmd_id)) = inserted.iter().find(|i| i.0 == id) { + let cmd = unsafe { command_queue.get_nth::>(*cmd_id) }; + let guard = cmd.component.try_write().expect("already borrowed"); + return Some(CellMut::new_overlay(guard)); + } + } + + Some(Self::wrap(inner, entity, refs)) + } + + fn fetch_overlay( + &mut self, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + todo!() + } +} + +macro_rules! impl_tuple_cell_fetch { + ($(($name: ident, $state: ident)),*) => { + #[allow(non_snake_case)] + impl<'world, 'state, $($name: CellFetch<'world, 'state>),*> CellFetch<'world, 'state> for ($($name,)*) { + type CellItem = ($($name::CellItem,)*); + + #[allow(clippy::unused_unit)] + #[inline] + fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { + let ($($name,)*) = inner; + ($(<$name as CellFetch<'world, 'state>>::wrap($name, _entity, _refs),)*) + } + + #[inline] + fn overlay( + inner: Self::Item, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + _command_queue: &'world CellCommandQueue, + ) -> Option { + let ($($name,)*) = inner; + Some(($(<$name as CellFetch<'world, 'state>>::overlay($name, _entity, _refs, _overlay, _components, _command_queue)?,)*)) + } + + #[inline] + fn fetch_overlay( + &mut self, + _entity: Entity, + _refs: &FetchRefs, + _overlay: &WorldOverlay, + _components: &Components, + ) -> Option { + let ($(ref mut $name,)*) = self; + Some(($(<$name as CellFetch<'world, 'state>>::fetch_overlay($name, _entity, _refs, _overlay, _components)?,)*)) + } + } + }; +} + +bevy_ecs_macros::all_tuples!(impl_tuple_cell_fetch, 0, 15, F, S); diff --git a/crates/bevy_ecs/src/world/world_cell/resource.rs b/crates/bevy_ecs/src/world/world_cell/resource.rs new file mode 100644 index 0000000000000..ba6f2b1900360 --- /dev/null +++ b/crates/bevy_ecs/src/world/world_cell/resource.rs @@ -0,0 +1,327 @@ +use crate::{ + archetype::ArchetypeComponentId, + storage::SparseSet, + system::Resource, + world::{Mut, WorldCell, WorldCellState}, +}; +use std::{ + any::TypeId, + ops::{Deref, DerefMut}, +}; + +pub(crate) struct ArchetypeComponentAccess { + access: SparseSet, +} + +const UNIQUE_ACCESS: u32 = 0; +const BASE_ACCESS: u32 = 1; +impl ArchetypeComponentAccess { + pub(super) const fn new() -> Self { + Self { + access: SparseSet::new(), + } + } + + fn read(&mut self, id: ArchetypeComponentId) -> bool { + let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); + if *id_access == UNIQUE_ACCESS { + false + } else { + *id_access += 1; + true + } + } + + fn drop_read(&mut self, id: ArchetypeComponentId) { + let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); + *id_access -= 1; + } + + fn write(&mut self, id: ArchetypeComponentId) -> bool { + let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); + if *id_access == BASE_ACCESS { + *id_access = UNIQUE_ACCESS; + true + } else { + false + } + } + + fn drop_write(&mut self, id: ArchetypeComponentId) { + let id_access = self.access.get_or_insert_with(id, || BASE_ACCESS); + *id_access = BASE_ACCESS; + } +} + +pub struct WorldCellRes<'w, T> { + value: &'w T, + archetype_component_id: ArchetypeComponentId, + state: &'w WorldCellState, +} + +impl<'w, T> WorldCellRes<'w, T> { + fn new( + value: &'w T, + archetype_component_id: ArchetypeComponentId, + state: &'w WorldCellState, + ) -> Self { + if !state + .resource_access + .borrow_mut() + .read(archetype_component_id) + { + panic!( + "Attempted to immutably access {}, but it is already mutably borrowed", + std::any::type_name::() + ) + } + Self { + value, + archetype_component_id, + state, + } + } +} + +impl<'w, T> Deref for WorldCellRes<'w, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'w, T> Drop for WorldCellRes<'w, T> { + fn drop(&mut self) { + let mut access = self.state.resource_access.borrow_mut(); + access.drop_read(self.archetype_component_id); + } +} + +pub struct WorldCellResMut<'w, T> { + value: Mut<'w, T>, + archetype_component_id: ArchetypeComponentId, + state: &'w WorldCellState, +} + +impl<'w, T> WorldCellResMut<'w, T> { + fn new( + value: Mut<'w, T>, + archetype_component_id: ArchetypeComponentId, + state: &'w WorldCellState, + ) -> Self { + if !state + .resource_access + .borrow_mut() + .write(archetype_component_id) + { + panic!( + "Attempted to mutably access {}, but it is already mutably borrowed", + std::any::type_name::() + ) + } + Self { + value, + archetype_component_id, + state, + } + } +} + +impl<'w, T> Deref for WorldCellResMut<'w, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.value.deref() + } +} + +impl<'w, T> DerefMut for WorldCellResMut<'w, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.value + } +} + +impl<'w, T> Drop for WorldCellResMut<'w, T> { + fn drop(&mut self) { + let mut access = self.state.resource_access.borrow_mut(); + access.drop_write(self.archetype_component_id); + } +} + +impl<'w> WorldCell<'w> { + pub fn get_resource(&self) -> Option> { + let component_id = self.world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = self.world.archetypes.resource(); + let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; + Some(WorldCellRes::new( + // SAFE: ComponentId matches TypeId + unsafe { self.world.get_resource_with_id(component_id)? }, + archetype_component_id, + &self.state, + )) + } + pub fn get_resource_mut(&self) -> Option> { + let component_id = self.world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = self.world.archetypes.resource(); + let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; + Some(WorldCellResMut::new( + // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut + unsafe { + self.world + .get_resource_unchecked_mut_with_id(component_id)? + }, + archetype_component_id, + &self.state, + )) + } + + pub fn get_non_send(&self) -> Option> { + let component_id = self.world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = self.world.archetypes.resource(); + let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; + Some(WorldCellRes::new( + // SAFE: ComponentId matches TypeId + unsafe { self.world.get_non_send_with_id(component_id)? }, + archetype_component_id, + &self.state, + )) + } + + pub fn get_non_send_mut(&self) -> Option> { + let component_id = self.world.components.get_resource_id(TypeId::of::())?; + let resource_archetype = self.world.archetypes.resource(); + let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; + Some(WorldCellResMut::new( + // SAFE: ComponentId matches TypeId and access is checked by WorldCellResMut + unsafe { + self.world + .get_non_send_unchecked_mut_with_id(component_id)? + }, + archetype_component_id, + &self.state, + )) + } +} + +#[cfg(test)] +mod tests { + use super::BASE_ACCESS; + use crate::{archetype::ArchetypeId, world::World}; + use std::any::TypeId; + + #[test] + fn world_cell_res_access() { + let mut world = World::default(); + world.insert_resource(1u32); + world.insert_resource(1u64); + let cell = world.cell(); + { + let mut a = cell.get_resource_mut::().unwrap(); + assert_eq!(1, *a); + *a = 2; + } + { + let a = cell.get_resource::().unwrap(); + assert_eq!(2, *a, "ensure access is dropped"); + + let b = cell.get_resource::().unwrap(); + assert_eq!( + 2, *b, + "ensure multiple immutable accesses can occur at the same time" + ); + } + { + let a = cell.get_resource_mut::().unwrap(); + assert_eq!( + 2, *a, + "ensure both immutable accesses are dropped, enabling a new mutable access" + ); + + let b = cell.get_resource::().unwrap(); + assert_eq!( + 1, *b, + "ensure multiple non-conflicting mutable accesses can occur at the same time" + ); + } + } + + #[test] + fn world_access_reused() { + let mut world = World::default(); + world.insert_resource(1u32); + { + let cell = world.cell(); + { + let mut a = cell.get_resource_mut::().unwrap(); + assert_eq!(1, *a); + *a = 2; + } + } + + let u32_component_id = world + .components + .get_resource_id(TypeId::of::()) + .unwrap(); + let resource_archetype = world.archetypes.get(ArchetypeId::RESOURCE).unwrap(); + let u32_archetype_component_id = resource_archetype + .get_archetype_component_id(u32_component_id) + .unwrap(); + assert_eq!( + world.world_cell_state.resource_access.borrow().access.len(), + 1 + ); + assert_eq!( + world + .world_cell_state + .resource_access + .borrow() + .access + .get(u32_archetype_component_id), + Some(&BASE_ACCESS), + "reused access count is 'base'" + ); + } + + #[test] + #[should_panic] + fn world_cell_double_mut() { + let mut world = World::default(); + world.insert_resource(1u32); + let cell = world.cell(); + let _value_a = cell.get_resource_mut::().unwrap(); + let _value_b = cell.get_resource_mut::().unwrap(); + } + + #[test] + #[should_panic] + fn world_cell_ref_and_mut() { + let mut world = World::default(); + world.insert_resource(1u32); + let cell = world.cell(); + let _value_a = cell.get_resource::().unwrap(); + let _value_b = cell.get_resource_mut::().unwrap(); + } + + #[test] + #[should_panic] + fn world_cell_mut_and_ref() { + let mut world = World::default(); + world.insert_resource(1u32); + let cell = world.cell(); + let _value_a = cell.get_resource_mut::().unwrap(); + let _value_b = cell.get_resource::().unwrap(); + } + + #[test] + #[should_panic] + fn world_cell_ref_and_ref() { + let mut world = World::default(); + world.insert_resource(1u32); + let cell = world.cell(); + let _value_a = cell.get_resource_mut::().unwrap(); + let _value_b = cell.get_resource::().unwrap(); + } +} From 0b3402675a398dfa5f0f5b6735ecfb822299fcbd Mon Sep 17 00:00:00 2001 From: Frizi Date: Mon, 8 Nov 2021 15:58:30 +0100 Subject: [PATCH 4/5] search for extra with/without matches in WorldCellQuery --- crates/bevy_ecs/src/world/append_list.rs | 2 +- crates/bevy_ecs/src/world/world_cell.rs | 68 +++-- crates/bevy_ecs/src/world/world_cell/query.rs | 245 ++++++++++++------ .../src/world/world_cell/query/fetch.rs | 232 ++++++++++++++--- 4 files changed, 417 insertions(+), 130 deletions(-) diff --git a/crates/bevy_ecs/src/world/append_list.rs b/crates/bevy_ecs/src/world/append_list.rs index a7eeca9e15ef6..f42fdab50d238 100644 --- a/crates/bevy_ecs/src/world/append_list.rs +++ b/crates/bevy_ecs/src/world/append_list.rs @@ -249,7 +249,7 @@ impl AppendList { self.check_invariants(); Iter { - list: &self, + list: self, index: 0, } } diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index fc394bf29790d..9e4e269989cb6 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -87,6 +87,32 @@ pub struct WorldOverlay { despawned_entities: HashSet, } +impl WorldOverlay { + fn potential_new_entities(&mut self, access: &FilteredAccess) -> HashSet { + let mut potential_new_entities = HashSet::default(); + for (entity, components) in &self.inserted { + for (id, _) in components { + if access.with().contains(id.index()) + || access.access().has_read(*id) + || access.access().has_write(*id) + { + potential_new_entities.insert(*entity); + break; + } + } + } + for (entity, components) in &self.removed { + for id in components { + if access.without().contains(id.index()) { + potential_new_entities.insert(*entity); + break; + } + } + } + potential_new_entities + } +} + pub trait CellCommand: Command { fn apply_overlay( &self, @@ -112,15 +138,12 @@ impl CellCommand for CellInsert { || access.access().has_write(id) { overlay.touched_entities.insert(self.entity); - match overlay.removed.entry(self.entity) { - Occupied(mut entry) => { - let v = entry.get_mut(); - v.retain(|c_id| *c_id != id); - if v.is_empty() { - entry.remove(); - } + if let Occupied(mut entry) = overlay.removed.entry(self.entity) { + let v = entry.get_mut(); + v.retain(|c_id| *c_id != id); + if v.is_empty() { + entry.remove(); } - _ => {} } overlay .inserted @@ -150,21 +173,22 @@ impl CellCommand for Remove { || access.access().has_write(id) { overlay.touched_entities.insert(self.entity); - match overlay.inserted.entry(self.entity) { - Occupied(mut entry) => { - let v = entry.get_mut(); - v.retain(|(c_id, _)| *c_id != id); - if v.is_empty() { - entry.remove(); - } + if let Occupied(mut entry) = overlay.inserted.entry(self.entity) { + let v = entry.get_mut(); + v.retain(|(c_id, _)| *c_id != id); + if v.is_empty() { + entry.remove(); } - _ => {} } - overlay.removed.entry(self.entity).and_modify(|v| { - if !v.contains(&id) { - v.push(id); - } - }); + overlay + .removed + .entry(self.entity) + .and_modify(|v| { + if !v.contains(&id) { + v.push(id); + } + }) + .or_insert_with(|| vec![id]); } } } @@ -180,6 +204,8 @@ impl CellCommand for Despawn { ) { overlay.touched_entities.insert(self.entity); overlay.despawned_entities.insert(self.entity); + overlay.inserted.remove(&self.entity); + overlay.removed.remove(&self.entity); } } diff --git a/crates/bevy_ecs/src/world/world_cell/query.rs b/crates/bevy_ecs/src/world/world_cell/query.rs index ab19f87798915..0132c701ba77a 100644 --- a/crates/bevy_ecs/src/world/world_cell/query.rs +++ b/crates/bevy_ecs/src/world/world_cell/query.rs @@ -1,18 +1,23 @@ mod fetch; use crate::{ - component::{ComponentId, Components}, + component::ComponentId, prelude::Entity, - query::{FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, - world::{CellCommandQueue, World, WorldCell, WorldCellState, WorldOverlay}, + query::{Fetch, FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, + world::{ + world_cell::query::fetch::OptQuery, CellCommandQueue, World, WorldCell, WorldCellState, + WorldOverlay, + }, }; use std::{ any::{Any, TypeId}, cell::Cell, + collections::hash_set::IntoIter, marker::PhantomData, rc::Rc, }; +use bevy_utils::HashSet; use fetch::CellFetch; pub(crate) use fetch::FetchRefs; pub use fetch::WorldCellQuery; @@ -20,13 +25,14 @@ pub use fetch::WorldCellQuery; pub(super) struct QueryCacheEntry { pub(super) alive_count: Cell, pub(super) in_working_set: Cell, + pub(super) opt_query: Box, pub(super) query: Q, } impl QueryCacheEntry { pub(super) fn alive_filtered_access(&self) -> Option<&FilteredAccess> { if self.alive_count.get() > 0 { - Some(&self.query.component_access()) + Some(self.query.component_access()) } else { None } @@ -66,7 +72,7 @@ pub struct CellQuery<'w, Q, F> { impl<'w, Q, F> CellQuery<'w, Q, F> where - Q: WorldCellQuery + 'static, + Q: WorldCellQuery + OptQuery + 'static, F: WorldCellQuery + 'static, F::Fetch: FilterFetch, { @@ -96,13 +102,29 @@ fn assert_component_access_compatibility( query_type, filter_type, accesses); } +enum CellQueryIterState<'w, 's, Q, F> +where + Q: WorldCellQuery + OptQuery, + F: WorldCellQuery, + F::Fetch: FilterFetch, +{ + Query { + iter: QueryIter<'w, 's, (Entity, Q), F>, + potential_new_entities: HashSet, + }, + Potential { + iter: IntoIter, + }, +} + pub struct CellQueryIter<'w, 's, Q, F> where - Q: WorldCellQuery, + Q: WorldCellQuery + OptQuery, F: WorldCellQuery, F::Fetch: FilterFetch, { - inner: QueryIter<'w, 's, (Entity, Q), F>, + iter: CellQueryIterState<'w, 's, Q, F>, + opt_query: &'s QueryState, // Rc holds data referenced in `inner`. Must be dropped last. // That Rc is normally held inside `WorldCellState` anyway, but holding it directly allows to guarantee // safety easier, as `WorldCellState` is now free to evict cache at any time without consequences @@ -110,13 +132,13 @@ where query_entry: Rc, refs: FetchRefs, command_queue: &'w CellCommandQueue, - components: &'w Components, + world: &'w World, overlay: WorldOverlay, } impl<'w, 's, Q, F> Drop for CellQueryIter<'w, 's, Q, F> where - Q: WorldCellQuery, + Q: WorldCellQuery + OptQuery, F: WorldCellQuery, F::Fetch: FilterFetch, { @@ -129,7 +151,7 @@ where impl<'w, 's, Q, F> CellQueryIter<'w, 's, Q, F> where - Q: WorldCellQuery + 'static, + Q: WorldCellQuery + OptQuery + 'static, F: WorldCellQuery + 'static, F::Fetch: FilterFetch, { @@ -140,6 +162,13 @@ where .as_any() .downcast_ref::>() .unwrap(); + let opt_query = cell_query + .query_entry + .opt_query + .as_any() + .downcast_ref::>() + .unwrap(); + // cast away the query_entry lifetime, so we can return an iterator that's self-referential // SAFETY: // - we hold onto the entry Rc for the entire lifetime of this reference, as it's cloned into returned WorldCellIter @@ -153,8 +182,8 @@ where std::any::type_name::(), std::any::type_name::(), &query.component_access, - &cell_query.world, - &cell_query.state, + cell_query.world, + cell_query.state, ); let inner = unsafe { @@ -175,16 +204,20 @@ where // prepare filters and modifiers based on current commands cell_query.state.command_queue.apply_overlay( &mut overlay, - &cell_query.world, + cell_query.world, &query.component_access, ); Self { query_entry, - inner, + iter: CellQueryIterState::Query { + iter: inner, + potential_new_entities: overlay.potential_new_entities(&query.component_access), + }, + opt_query, refs: cell_query.state.current_query_refs.clone(), command_queue: &cell_query.state.command_queue, - components: &cell_query.world.components, + world: cell_query.world, overlay, } } @@ -192,71 +225,111 @@ where impl<'w, 's, Q, F> Iterator for CellQueryIter<'w, 's, Q, F> where - Q: WorldCellQuery, + Q: WorldCellQuery + OptQuery, F: WorldCellQuery, F::Fetch: FilterFetch, + ::CellFetch: CellFetch< + 'w, + 's, + OptItem = <<::OptQuery as WorldQuery>::Fetch as Fetch<'w, 's>>::Item, + >, { type Item = >::CellItem; fn next(&mut self) -> Option { loop { - let (entity, data) = self.inner.next()?; - // no processing necessary - if !self.overlay.touched_entities.contains(&entity) { - return Some(Q::CellFetch::wrap(data, entity, &self.refs)); - } - - if self.overlay.despawned_entities.contains(&entity) { - continue; - } - - // TODO: filter out with/without - match Q::CellFetch::overlay( - data, - entity, - &self.refs, - &self.overlay, - self.components, - &self.command_queue, - ) { - Some(data) => return Some(data), - None => continue, + match &mut self.iter { + CellQueryIterState::Query { + iter, + potential_new_entities, + } => { + for (entity, data) in iter { + // no processing necessary + if !self.overlay.touched_entities.contains(&entity) { + return Some(Q::CellFetch::wrap(data, entity, &self.refs)); + } + + if self.overlay.despawned_entities.contains(&entity) { + continue; + } + + if let Some(data) = Q::CellFetch::overlay( + data, + entity, + &self.refs, + &self.overlay, + self.world.components(), + self.command_queue, + ) { + potential_new_entities.remove(&entity); + return Some(data); + } + } + if let CellQueryIterState::Query { + potential_new_entities, + .. + } = std::mem::replace( + &mut self.iter, + CellQueryIterState::Potential { + iter: HashSet::default().into_iter(), + }, + ) { + self.iter = CellQueryIterState::Potential { + iter: potential_new_entities.into_iter(), + }; + } + } + // handle extra matches + CellQueryIterState::Potential { iter } => { + for potential_match in iter { + if let Ok(raw) = unsafe { + self.opt_query.get_unchecked_manual( + self.world, + potential_match, + self.world.last_change_tick(), + self.world.read_change_tick(), + ) + } { + if let Some(data) = Q::CellFetch::fetch_overlay( + raw, + potential_match, + &self.refs, + &self.overlay, + self.world.components(), + self.command_queue, + ) { + return Some(data); + } + } + } + return None; + } } } - - // TODO: handle extra matches } fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} - -// not colliding queries: -// q1: <&mut A, Without> -// q2: <&mut A, With> -// q1, insert B, q2 - -impl<'w, 's, Q, F> ExactSizeIterator for CellQueryIter<'w, 's, Q, F> -where - Q: WorldCellQuery, - F: WorldCellQuery, - F::Fetch: FilterFetch, - QueryIter<'w, 's, (Entity, Q), F>: ExactSizeIterator, -{ - fn len(&self) -> usize { - self.inner.len() + match &self.iter { + CellQueryIterState::Query { + iter, + potential_new_entities, + } => { + let (min, max) = iter.size_hint(); + (min, max.map(|x| x + potential_new_entities.len())) + } + CellQueryIterState::Potential { iter } => iter.size_hint(), + } } } impl<'w> WorldCell<'w> { - pub fn init_query(&mut self) -> QueryToken { + pub fn init_query(&mut self) -> QueryToken { self.init_filtered_query() } pub fn init_filtered_query(&mut self) -> QueryToken where - Q: WorldCellQuery + 'static, + Q: WorldCellQuery + OptQuery + 'static, F: WorldCellQuery + 'static, F::Fetch: FilterFetch, { @@ -266,6 +339,7 @@ impl<'w> WorldCell<'w> { Rc::new(QueryCacheEntry { alive_count: Cell::new(0), in_working_set: Cell::new(false), + opt_query: Box::new(world.query::()), query: world.query_filtered::<(Entity, Q), F>(), }) }); @@ -303,7 +377,7 @@ impl<'w> WorldCell<'w> { CellQuery { query_entry: query_entry.clone(), state: &self.state, - world: &self.world, + world: self.world, marker: PhantomData, } } @@ -313,7 +387,7 @@ impl<'w> WorldCell<'w> { mod tests { use crate::{ self as bevy_ecs, - prelude::{Component, Entity, Without}, + prelude::{Component, Entity, With, Without}, world::{QueryToken, World, WorldCell}, }; @@ -483,38 +557,53 @@ mod tests { fn world_cell_query_overlay() { let mut world = World::default(); - world.spawn().insert(A).insert(C(0)); world.spawn().insert(A).insert(C(1)); + world.spawn().insert(A); world.spawn().insert(A).insert(C(2)); // world.spawn() let mut cell = world.cell(); - let t1 = cell.init_query::<(Entity, &A)>(); + let t1 = cell.init_query::<(Entity, &A, Option<&C>)>(); let t2 = cell.init_query::<(&A, &C)>(); let q1 = cell.query(t1); let q2 = cell.query(t2); let mut vals = Vec::new(); - for (i, (entity, _)) in q1.iter().enumerate() { - cell.entity(entity).insert(C(10 + i)); - for (a, c) in q2.iter() { - vals.push((a.clone(), c.clone())); + for (entity, _, c) in q1.iter() { + cell.entity(entity).insert(C(c.map_or(0, |c| c.0) + 10)); + for (_, c) in q2.iter() { + vals.push(c.clone()); } } assert_eq!( vals, - vec![ - (A, C(10)), - (A, C(1)), - (A, C(2)), - (A, C(10)), - (A, C(11)), - (A, C(2)), - (A, C(10)), - (A, C(11)), - (A, C(12)) - ] + vec![C(1), C(2), C(10), C(11), C(2), C(10), C(11), C(12), C(10)] ); } + + #[test] + fn world_cell_query_without_insert() { + let mut world = World::default(); + + let _e0 = world.spawn().insert(C(1)).id(); + let e1 = world.spawn().insert(A).insert(C(2)).id(); + let e2 = world.spawn().insert(A).id(); + let mut cell = world.cell(); + + let token1 = cell.init_filtered_query(); + // let token2 = cell.init_filtered_query(); + + let query1 = cell.query::<(Entity, With, Without), ()>(token1); + // let query2 = cell.query::, Without)>(token2); + + assert_eq!(query1.iter().collect::>(), vec![(e2, true, true)]); + // assert_eq!(query2.iter().collect::>(), vec![e2]); + + cell.entity(e1).remove::(); + cell.entity(e2).insert(C(3)); + + assert_eq!(query1.iter().collect::>(), vec![(e1, true, true)]); + // assert_eq!(query2.iter().collect::>(), vec![e1]); + } } diff --git a/crates/bevy_ecs/src/world/world_cell/query/fetch.rs b/crates/bevy_ecs/src/world/world_cell/query/fetch.rs index 577875b937c11..9000b374e5a49 100644 --- a/crates/bevy_ecs/src/world/world_cell/query/fetch.rs +++ b/crates/bevy_ecs/src/world/world_cell/query/fetch.rs @@ -9,8 +9,10 @@ use bevy_utils::HashMap; use crate::{ component::{Component, Components}, - prelude::{Entity, Mut}, - query::{EntityFetch, Fetch, ReadFetch, WithFetch, WithoutFetch, WorldQuery, WriteFetch}, + prelude::{Entity, Mut, With, Without}, + query::{ + EntityFetch, Fetch, OptionFetch, ReadFetch, WithFetch, WithoutFetch, WorldQuery, WriteFetch, + }, world::{world_cell::command::CellInsert, CellCommandQueue, WorldOverlay}, }; @@ -185,6 +187,34 @@ pub trait WorldCellQuery: WorldQuery { >; } +pub trait OptQuery { + type OptQuery: WorldQuery; +} + +impl OptQuery for &T { + type OptQuery = Option; +} + +impl OptQuery for &mut T { + type OptQuery = Option; +} + +impl OptQuery for Entity { + type OptQuery = Self; +} + +impl OptQuery for With { + type OptQuery = Option; +} + +impl OptQuery for Without { + type OptQuery = Option; +} + +impl OptQuery for Option { + type OptQuery = Self; +} + impl WorldCellQuery for T where T: WorldQuery, @@ -198,6 +228,7 @@ where pub trait CellFetch<'world, 'state>: Fetch<'world, 'state> { type CellItem; + type OptItem; // just wrap original data without further filtering fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem; @@ -213,16 +244,18 @@ pub trait CellFetch<'world, 'state>: Fetch<'world, 'state> { // fetch using combined world and overlay data fn fetch_overlay( - &mut self, + opt_inner: Self::OptItem, entity: Entity, refs: &FetchRefs, overlay: &WorldOverlay, components: &Components, + command_queue: &'world CellCommandQueue, ) -> Option; } impl<'world, 'state> CellFetch<'world, 'state> for EntityFetch { type CellItem = >::Item; + type OptItem = Self::Item; fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { inner } @@ -240,11 +273,12 @@ impl<'world, 'state> CellFetch<'world, 'state> for EntityFetch { #[inline] fn fetch_overlay( - &mut self, + _opt_inner: Self::OptItem, entity: Entity, _refs: &FetchRefs, _overlay: &WorldOverlay, _components: &Components, + _command_queue: &'world CellCommandQueue, ) -> Option { Some(entity) } @@ -252,6 +286,7 @@ impl<'world, 'state> CellFetch<'world, 'state> for EntityFetch { impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithFetch { type CellItem = >::Item; + type OptItem = as Fetch<'world, 'state>>::Item; fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { inner } @@ -274,18 +309,40 @@ impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithFetch { } fn fetch_overlay( - &mut self, - _entity: Entity, + opt_inner: Self::OptItem, + entity: Entity, _refs: &FetchRefs, - _overlay: &WorldOverlay, - _components: &Components, + overlay: &WorldOverlay, + components: &Components, + _command_queue: &'world CellCommandQueue, ) -> Option { - todo!() + // two options to pass "With": + // - world has the data and it wasn't deleted + // - overlay has the data + let id = components.get_id(TypeId::of::())?; + + if opt_inner == Some(true) { + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return None; + } + } + return Some(true); + } + + if let Some(inserted) = overlay.inserted.get(&entity) { + if inserted.iter().any(|i| i.0 == id) { + return Some(true); + } + } + + None } } impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithoutFetch { type CellItem = >::Item; + type OptItem = as Fetch<'world, 'state>>::Item; fn wrap(inner: Self::Item, _entity: Entity, _refs: &FetchRefs) -> Self::CellItem { inner } @@ -300,7 +357,7 @@ impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithoutFetch ) -> Option { if let Some(inserted) = overlay.inserted.get(&entity) { let id = components.get_id(TypeId::of::())?; - if inserted.iter().find(|i| i.0 == id).is_some() { + if inserted.iter().any(|i| i.0 == id) { return None; } } @@ -308,18 +365,79 @@ impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WithoutFetch } fn fetch_overlay( - &mut self, - _entity: Entity, + opt_inner: Self::OptItem, + entity: Entity, _refs: &FetchRefs, - _overlay: &WorldOverlay, - _components: &Components, + overlay: &WorldOverlay, + components: &Components, + _command_queue: &'world CellCommandQueue, + ) -> Option { + // two options to pass "Without": + // - overlay not added or deleted the data + // - world has no data + let id = components.get_id(TypeId::of::())?; + + if opt_inner == Some(true) { + if let Some(inserted) = overlay.inserted.get(&entity) { + if inserted.iter().any(|i| i.0 == id) { + return None; + } + } + return Some(true); + } + + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return Some(true); + } + } + + None + } +} + +impl<'world, 'state, T: CellFetch<'world, 'state>> CellFetch<'world, 'state> for OptionFetch { + type CellItem = Option; + type OptItem = T::OptItem; + + fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem { + inner.map(|inner| T::wrap(inner, entity, refs)) + } + + fn overlay( + inner: Self::Item, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, + ) -> Option { + inner.map(|inner| T::overlay(inner, entity, refs, overlay, components, command_queue)) + } + + fn fetch_overlay( + opt_inner: Self::OptItem, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, ) -> Option { - todo!() + Some(T::fetch_overlay( + opt_inner, + entity, + refs, + overlay, + components, + command_queue, + )) } } impl<'world, 'state, T: Component> CellFetch<'world, 'state> for ReadFetch { type CellItem = CellRef<'world, T>; + type OptItem = as Fetch<'world, 'state>>::Item; + fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem { CellRef::new(inner, entity, refs) } @@ -352,18 +470,42 @@ impl<'world, 'state, T: Component> CellFetch<'world, 'state> for ReadFetch { } fn fetch_overlay( - &mut self, - _entity: Entity, - _refs: &FetchRefs, - _overlay: &WorldOverlay, - _components: &Components, + opt_inner: Self::OptItem, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, ) -> Option { - todo!() + // three potential cases: + // - overlay has the data (inserted) + // - world has the data, overlay doesn't remove it + // - no data + let id = components.get_id(TypeId::of::())?; + + if let Some(inserted) = overlay.inserted.get(&entity) { + if let Some((_, cmd_id)) = inserted.iter().find(|i| i.0 == id) { + let cmd = unsafe { command_queue.get_nth::>(*cmd_id) }; + let guard = cmd.component.try_read().expect("already borrowed"); + return Some(CellRef::new_overlay(guard)); + } + } + + if let Some(inner) = opt_inner { + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return None; + } + } + return Some(Self::wrap(inner, entity, refs)); + } + None } } impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WriteFetch { type CellItem = CellMut<'world, T>; + type OptItem = as Fetch<'world, 'state>>::Item; fn wrap(inner: Self::Item, entity: Entity, refs: &FetchRefs) -> Self::CellItem { CellMut::new(inner, entity, refs) } @@ -396,21 +538,50 @@ impl<'world, 'state, T: Component> CellFetch<'world, 'state> for WriteFetch { } fn fetch_overlay( - &mut self, - _entity: Entity, - _refs: &FetchRefs, - _overlay: &WorldOverlay, - _components: &Components, + opt_inner: Self::OptItem, + entity: Entity, + refs: &FetchRefs, + overlay: &WorldOverlay, + components: &Components, + command_queue: &'world CellCommandQueue, ) -> Option { - todo!() + // three potential cases: + // - overlay has the data (inserted) + // - world has the data, overlay doesn't remove it + // - no data + let id = components.get_id(TypeId::of::())?; + + if let Some(inserted) = overlay.inserted.get(&entity) { + if let Some((_, cmd_id)) = inserted.iter().find(|i| i.0 == id) { + let cmd = unsafe { command_queue.get_nth::>(*cmd_id) }; + let guard = cmd.component.try_write().expect("already borrowed"); + return Some(CellMut::new_overlay(guard)); + } + } + + if let Some(inner) = opt_inner { + if let Some(removed) = overlay.removed.get(&entity) { + if removed.contains(&id) { + return None; + } + } + return Some(Self::wrap(inner, entity, refs)); + } + None } } macro_rules! impl_tuple_cell_fetch { ($(($name: ident, $state: ident)),*) => { + #[allow(non_snake_case)] + impl<$($name: OptQuery),*> OptQuery for ($($name,)*) { + type OptQuery = ($($name::OptQuery,)*); + } + #[allow(non_snake_case)] impl<'world, 'state, $($name: CellFetch<'world, 'state>),*> CellFetch<'world, 'state> for ($($name,)*) { type CellItem = ($($name::CellItem,)*); + type OptItem = ($($name::OptItem,)*); #[allow(clippy::unused_unit)] #[inline] @@ -434,14 +605,15 @@ macro_rules! impl_tuple_cell_fetch { #[inline] fn fetch_overlay( - &mut self, + opt_inner: Self::OptItem, _entity: Entity, _refs: &FetchRefs, _overlay: &WorldOverlay, _components: &Components, + _command_queue: &'world CellCommandQueue, ) -> Option { - let ($(ref mut $name,)*) = self; - Some(($(<$name as CellFetch<'world, 'state>>::fetch_overlay($name, _entity, _refs, _overlay, _components)?,)*)) + let ($($name,)*) = opt_inner; + Some(($(<$name as CellFetch<'world, 'state>>::fetch_overlay($name, _entity, _refs, _overlay, _components, _command_queue)?,)*)) } } }; From 15589aa4b3b097b1c187d127b3c7d13687160806 Mon Sep 17 00:00:00 2001 From: Frizi Date: Tue, 9 Nov 2021 12:23:08 +0100 Subject: [PATCH 5/5] remove eager query conflict analysis in WorldCell queries --- crates/bevy_ecs/src/world/world_cell.rs | 28 ----- .../bevy_ecs/src/world/world_cell/command.rs | 4 - crates/bevy_ecs/src/world/world_cell/query.rs | 114 +++++++++--------- 3 files changed, 58 insertions(+), 88 deletions(-) diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 9e4e269989cb6..4bde892341f97 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -33,8 +33,6 @@ pub struct WorldCell<'w> { pub(crate) struct WorldCellState { resource_access: RefCell, query_cache: HashMap, fxhash::FxBuildHasher>, - /// Queries that were activated at least once in the current WorldCell session. - query_cache_working_set: RefCell>>, command_queue: CellCommandQueue, current_query_refs: FetchRefs, } @@ -47,38 +45,12 @@ impl WorldCellState { resource_access: RefCell::new(ArchetypeComponentAccess::new()), // component_access: RefCell::new(ComponentAccess::new()), query_cache: HashMap::default(), - query_cache_working_set: Default::default(), command_queue: Default::default(), current_query_refs: Default::default(), } } - - fn get_live_query_conflicts_filtered( - &self, - filtered_access: &FilteredAccess, - ) -> Vec { - for query in self.query_cache_working_set.borrow().iter() { - if let Some(current_filtered_access) = query.alive_filtered_access() { - if !current_filtered_access.is_compatible(filtered_access) { - return current_filtered_access - .access() - .get_conflicts(filtered_access.access()); - } - } - } - Vec::new() - } } -// how to merge real result with overlay? -// how to handle inserts that results in query visiting new element? -// first: prepare set of types that influence query -// - how to handle Without? Only deletions (how?) and inserts (filter out) matter -// - how to handle With? only deletions (filter out) and inserts (how?) matter -// -// create a temp world that only contains the affected entities as clones? -// create a structure that describes the "diff" internal structure as a pass-through API - #[derive(Default)] pub struct WorldOverlay { touched_entities: HashSet, diff --git a/crates/bevy_ecs/src/world/world_cell/command.rs b/crates/bevy_ecs/src/world/world_cell/command.rs index 5f4ceda6b2aa9..b600eeb91d84c 100644 --- a/crates/bevy_ecs/src/world/world_cell/command.rs +++ b/crates/bevy_ecs/src/world/world_cell/command.rs @@ -105,10 +105,6 @@ impl<'w> WorldCell<'w> { /// A WorldCell session "barrier". Applies world commands issued thus far, optimizing future query accesses. pub fn maintain(&mut self) { - // Clear working set when the WorldCell session ends. - for entry in self.state.query_cache_working_set.get_mut().drain(..) { - entry.in_working_set.set(false); - } self.state.command_queue.apply(self.world); } } diff --git a/crates/bevy_ecs/src/world/world_cell/query.rs b/crates/bevy_ecs/src/world/world_cell/query.rs index 0132c701ba77a..5ee1d08361b37 100644 --- a/crates/bevy_ecs/src/world/world_cell/query.rs +++ b/crates/bevy_ecs/src/world/world_cell/query.rs @@ -1,9 +1,8 @@ mod fetch; use crate::{ - component::ComponentId, prelude::Entity, - query::{Fetch, FilterFetch, FilteredAccess, QueryIter, QueryState, WorldQuery}, + query::{Fetch, FilterFetch, QueryIter, QueryState, WorldQuery}, world::{ world_cell::query::fetch::OptQuery, CellCommandQueue, World, WorldCell, WorldCellState, WorldOverlay, @@ -24,23 +23,11 @@ pub use fetch::WorldCellQuery; pub(super) struct QueryCacheEntry { pub(super) alive_count: Cell, - pub(super) in_working_set: Cell, pub(super) opt_query: Box, pub(super) query: Q, } -impl QueryCacheEntry { - pub(super) fn alive_filtered_access(&self) -> Option<&FilteredAccess> { - if self.alive_count.get() > 0 { - Some(self.query.component_access()) - } else { - None - } - } -} - pub(super) trait DynQueryState: Any { - fn component_access(&self) -> &FilteredAccess; fn as_any(&self) -> &dyn Any; } @@ -48,9 +35,6 @@ impl DynQueryState for QuerySt where F::Fetch: FilterFetch, { - fn component_access(&self) -> &FilteredAccess { - &self.component_access - } fn as_any(&self) -> &dyn Any { self } @@ -82,26 +66,6 @@ where } } -fn assert_component_access_compatibility( - query_type: &'static str, - filter_type: &'static str, - current: &FilteredAccess, - world: &World, - state: &WorldCellState, -) { - let mut conflicts = state.get_live_query_conflicts_filtered(current); - if conflicts.is_empty() { - return; - } - let conflicting_components = conflicts - .drain(..) - .map(|component_id| world.components.get_info(component_id).unwrap().name()) - .collect::>(); - let accesses = conflicting_components.join(", "); - panic!("CellQuery<{}, {}> in WorldCell accesses component(s) {} in a way that conflicts with other active access. Allowing this would break Rust's mutability rules. Consider using `Without` to create disjoint Queries.", - query_type, filter_type, accesses); -} - enum CellQueryIterState<'w, 's, Q, F> where Q: WorldCellQuery + OptQuery, @@ -178,14 +142,6 @@ where .unwrap() }; - assert_component_access_compatibility( - std::any::type_name::(), - std::any::type_name::(), - &query.component_access, - cell_query.world, - cell_query.state, - ); - let inner = unsafe { query.iter_unchecked_manual( cell_query.world, @@ -338,7 +294,6 @@ impl<'w> WorldCell<'w> { self.state.query_cache.entry(key).or_insert_with(|| { Rc::new(QueryCacheEntry { alive_count: Cell::new(0), - in_working_set: Cell::new(false), opt_query: Box::new(world.query::()), query: world.query_filtered::<(Entity, Q), F>(), }) @@ -364,16 +319,6 @@ impl<'w> WorldCell<'w> { .get(&key) .expect("token cannot exist without initialization"); - // the token existence guarantees that the query was initialized, but not necessarily in the same WorldCell session. - // So instead of during initialization, we add queries to working set at the first use in each session. - if !query_entry.in_working_set.get() { - query_entry.in_working_set.set(true); - self.state - .query_cache_working_set - .borrow_mut() - .push(query_entry.clone()); - } - CellQuery { query_entry: query_entry.clone(), state: &self.state, @@ -606,4 +551,61 @@ mod tests { assert_eq!(query1.iter().collect::>(), vec![(e1, true, true)]); // assert_eq!(query2.iter().collect::>(), vec![e1]); } + + #[test] + fn world_cell_query_insert_before_after_iter() { + let mut world = World::default(); + + let e0 = world.spawn().insert(A).id(); + + let e1 = world.spawn().id(); + let e2 = world.spawn().id(); + + let mut cell = world.cell(); + let token = cell.init_query(); + + let query = cell.query::<(Entity, With), _>(token); + + let iter1 = query.iter(); + + cell.entity(e1).insert(A); + + let iter2 = query.iter(); + + cell.entity(e2).insert(A); + + let iter3 = query.iter(); + + assert_eq!(iter1.collect::>(), vec![(e0, true)]); + assert_eq!(iter2.collect::>(), vec![(e0, true), (e1, true)]); + assert_eq!( + iter3.collect::>(), + vec![(e0, true), (e1, true), (e2, true)] + ); + } + + #[test] + fn world_cell_query_ref_mut_at_once() { + let mut world = World::default(); + + world.spawn().insert(C(1)); + world.spawn().insert(A).insert(C(2)); + world.spawn().insert(A).insert(C(3)); + + let mut cell = world.cell(); + + let token1 = cell.init_query(); + let token2 = cell.init_query(); + + let query_ref = cell.query::<(Entity, &C), _>(token1); + let query_mut = cell.query::<(Entity, &mut C), _>(token2); + let iter_ref = query_ref.iter(); + let mut iter_mut = query_mut.iter(); + + iter_mut.next(); // offset ope query to avoid conflicts + + for (previous, mut next) in iter_ref.zip(iter_mut) { + *next.1 = previous.1.clone(); + } + } }