From 1d7ef6786410c4a5e98854c79f9fcff279e4fe97 Mon Sep 17 00:00:00 2001 From: Zakarum Date: Tue, 16 Jul 2024 20:48:46 +0200 Subject: [PATCH] Improve relations --- Cargo.toml | 4 +- examples/relation.rs | 10 +- proc/Cargo.toml | 2 +- src/action/channel.rs | 4 +- src/action/encoder.rs | 159 +++++++- src/entity/entity.rs | 6 - src/flow/world.rs | 4 +- src/relation/components.rs | 374 ++++++++++++++++++ src/relation/mod.rs | 407 +++---------------- src/relation/query/filter_related_by.rs | 9 +- src/relation/query/filter_relates_to.rs | 4 +- src/relation/query/iter.rs | 238 ++++++++++++ src/relation/query/mod.rs | 11 +- src/relation/query/related.rs | 495 ++++++++++++++++++++++-- src/relation/query/relates.rs | 352 ++++++++--------- src/relation/query/relates_exclusive.rs | 233 ++++++++--- src/relation/query/relates_to.rs | 24 +- src/scheduler.rs | 4 +- src/system/func/mod.rs | 32 +- src/system/mod.rs | 8 +- src/test.rs | 38 +- src/view/extend.rs | 46 ++- src/world/relation.rs | 58 +-- src/world/remove.rs | 61 ++- src/world/spawn.rs | 36 ++ 25 files changed, 1874 insertions(+), 745 deletions(-) create mode 100644 src/relation/components.rs create mode 100644 src/relation/query/iter.rs diff --git a/Cargo.toml b/Cargo.toml index b247479..de6a777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = ["proc-lib", "proc"] [workspace.package] -version = "1.0.0-rc5" +version = "1.0.0-rc6" edition = "2021" authors = ["Zakarum "] license = "MIT OR Apache-2.0" @@ -43,7 +43,7 @@ default = ["std", "scheduler", "flow"] rayon = ["dep:rayon", "std"] [dependencies] -edict-proc = { version = "=1.0.0-rc5", path = "proc" } +edict-proc = { version = "=1.0.0-rc6", path = "proc" } amity = { version = "0.2.1", default-features = false, features = ["alloc"] } hashbrown = { version = "0.14" } smallvec = { version = "1.10", features = ["union"], default-features = false } diff --git a/examples/relation.rs b/examples/relation.rs index 9102d03..c3decc1 100644 --- a/examples/relation.rs +++ b/examples/relation.rs @@ -44,7 +44,7 @@ fn main() { let a = world.spawn((A,)).id(); let b = world.spawn(()).id(); - world.add_relation(a, ChildOf, b).unwrap(); + world.insert_relation(a, ChildOf, b).unwrap(); for (e, ChildOf) in world.view::().relates_to::(b).iter() { println!("{} is child of {}", e, b); @@ -58,8 +58,8 @@ fn main() { let b = world.spawn(()).id(); let c = world.spawn(()).id(); - world.add_relation(a, Likes, b).unwrap(); - world.add_relation(a, Likes, c).unwrap(); + world.insert_relation(a, Likes, b).unwrap(); + world.insert_relation(a, Likes, c).unwrap(); let mut view = world.get::>(a).unwrap(); let first = view.next().unwrap(); @@ -75,7 +75,7 @@ fn main() { let b = world.spawn(()).id(); - world.add_relation(a, Enemy, b).unwrap(); + world.insert_relation(a, Enemy, b).unwrap(); let q = world.view::().relates::(); for (e, enemies) in q.iter() { @@ -113,7 +113,7 @@ fn main() { let a = world.spawn((A,)).id(); let b = world.spawn((B,)).id(); - world.add_relation(a, LifeBound, b).unwrap(); + world.insert_relation(a, LifeBound, b).unwrap(); world.despawn(a).unwrap(); assert_eq!(world.is_alive(b), false); diff --git a/proc/Cargo.toml b/proc/Cargo.toml index d342fe0..099acb5 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -17,4 +17,4 @@ proc-macro = true [dependencies] syn = "2.0" -edict-proc-lib = { version = "=1.0.0-rc5", path = "../proc-lib" } +edict-proc-lib = { version = "=1.0.0-rc6", path = "../proc-lib" } diff --git a/src/action/channel.rs b/src/action/channel.rs index 7109d05..38c2482 100644 --- a/src/action/channel.rs +++ b/src/action/channel.rs @@ -223,12 +223,12 @@ impl ActionSender { /// Encodes an action to add relation between two entities to the [`World`]. #[inline(always)] - pub fn add_relation(&self, origin: EntityId, relation: R, target: EntityId) + pub fn insert_relation(&self, origin: EntityId, relation: R, target: EntityId) where R: Relation + Send, { self.push_fn(move |world| { - let _ = world.add_relation(origin, relation, target); + let _ = world.insert_relation(origin, relation, target); }); } diff --git a/src/action/encoder.rs b/src/action/encoder.rs index cbdb548..34eab0e 100644 --- a/src/action/encoder.rs +++ b/src/action/encoder.rs @@ -1,6 +1,7 @@ use core::{any::TypeId, iter::FusedIterator}; use alloc::collections::VecDeque; +use smallvec::SmallVec; use crate::{ bundle::{Bundle, ComponentBundle, DynamicBundle, DynamicComponentBundle}, @@ -123,6 +124,39 @@ impl<'a> ActionEncoder<'a> { }) } + /// Encodes an action to despawn entities in batch. + #[inline(always)] + pub fn despawn_batch(&mut self, entities: impl IntoIterator) { + let entities = entities.into_iter(); + + match entities.size_hint() { + (_, Some(upper)) if upper <= 8 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + (_, Some(upper)) if upper <= 16 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + (_, Some(upper)) if upper <= 32 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + _ => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + } + } + /// Encodes an action to insert component to the specified entity. #[inline(always)] pub fn insert(&mut self, entity: impl Entity, component: T) @@ -230,6 +264,15 @@ impl<'a> ActionEncoder<'a> { self.drop_erased(entity, type_id::()) } + /// Encodes an action to drop component from entities in batch. + #[inline(always)] + pub fn drop_batch(&mut self, entities: impl IntoIterator) + where + T: 'static, + { + self.drop_erased_batch(entities, type_id::()) + } + /// Encodes an action to drop component from specified entity. #[inline(always)] pub fn drop_erased(&mut self, entity: impl Entity, ty: TypeId) { @@ -239,6 +282,39 @@ impl<'a> ActionEncoder<'a> { }) } + /// Encodes an action to drop component from entities in batch. + #[inline(always)] + pub fn drop_erased_batch(&mut self, entities: impl IntoIterator, ty: TypeId) { + let entities = entities.into_iter(); + + match entities.size_hint() { + (_, Some(upper)) if upper <= 8 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + (_, Some(upper)) if upper <= 16 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + (_, Some(upper)) if upper <= 32 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + _ => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + } + } + /// Encodes an action to drop bundle of components from specified entity. #[inline(always)] pub fn drop_bundle(&mut self, entity: impl Entity) @@ -253,14 +329,14 @@ impl<'a> ActionEncoder<'a> { /// Encodes an action to add relation between two entities to the [`World`]. #[inline(always)] - pub fn add_relation(&mut self, origin: impl Entity, relation: R, target: impl Entity) + pub fn insert_relation(&mut self, origin: impl Entity, relation: R, target: impl Entity) where R: Relation + Send, { let origin = origin.id(); let target = target.id(); self.push_fn(move |world| { - let _ = world.add_relation(origin, relation, target); + let _ = world.insert_relation(origin, relation, target); }); } @@ -564,6 +640,39 @@ impl<'a> LocalActionEncoder<'a> { }) } + /// Encodes an action to despawn entities in batch. + #[inline(always)] + pub fn despawn_batch(&mut self, entities: impl IntoIterator) { + let entities = entities.into_iter(); + + match entities.size_hint() { + (_, Some(upper)) if upper <= 8 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + (_, Some(upper)) if upper <= 16 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + (_, Some(upper)) if upper <= 32 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + _ => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.despawn_batch(entities); + }); + } + } + } + /// Encodes an action to insert component to the specified entity. #[inline(always)] pub fn insert(&mut self, entity: impl Entity, component: T) @@ -687,6 +796,15 @@ impl<'a> LocalActionEncoder<'a> { self.drop_erased(entity, type_id::()) } + /// Encodes an action to drop component from entities in batch. + #[inline(always)] + pub fn drop_batch(&mut self, entities: impl IntoIterator) + where + T: 'static, + { + self.drop_erased_batch(entities, type_id::()) + } + /// Encodes an action to drop component from specified entity. #[inline(always)] pub fn drop_erased(&mut self, entity: impl Entity, ty: TypeId) { @@ -696,6 +814,39 @@ impl<'a> LocalActionEncoder<'a> { }) } + /// Encodes an action to drop component from entities in batch. + #[inline(always)] + pub fn drop_erased_batch(&mut self, entities: impl IntoIterator, ty: TypeId) { + let entities = entities.into_iter(); + + match entities.size_hint() { + (_, Some(upper)) if upper <= 8 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + (_, Some(upper)) if upper <= 16 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + (_, Some(upper)) if upper <= 32 => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + _ => { + let entities = entities.into_iter().collect::>(); + self.push_fn(move |world| { + let _ = world.drop_erased_batch(entities, ty); + }); + } + } + } + /// Encodes an action to drop bundle of components from specified entity. #[inline(always)] pub fn drop_bundle(&mut self, entity: impl Entity) @@ -710,14 +861,14 @@ impl<'a> LocalActionEncoder<'a> { /// Encodes an action to add relation between two entities to the [`World`]. #[inline(always)] - pub fn add_relation(&mut self, origin: impl Entity, relation: R, target: impl Entity) + pub fn insert_relation(&mut self, origin: impl Entity, relation: R, target: impl Entity) where R: Relation, { let origin = origin.id(); let target = target.id(); self.push_fn(move |world| { - let _ = world.add_relation(origin, relation, target); + let _ = world.insert_relation(origin, relation, target); }); } diff --git a/src/entity/entity.rs b/src/entity/entity.rs index 1864c35..02b1cf2 100644 --- a/src/entity/entity.rs +++ b/src/entity/entity.rs @@ -239,12 +239,6 @@ impl EntityBound<'_> { world: PhantomData, } } - - #[inline(always)] - pub(crate) fn wrap_slice(ids: &[EntityId]) -> &[Self] { - // Safety: `Self` is transparent wrapper over `EntityId`. - unsafe { &*(ids as *const [EntityId] as *const [Self]) } - } } impl<'a> Entity for EntityBound<'a> { diff --git a/src/flow/world.rs b/src/flow/world.rs index 7a2b8a0..db994a3 100644 --- a/src/flow/world.rs +++ b/src/flow/world.rs @@ -610,7 +610,7 @@ impl FlowWorld { /// If relation is exclusive, then previous relation on origin is replaced, otherwise relation is added. /// If relation is exclusive and symmetric, then previous relation on target is replaced, otherwise relation is added. #[inline(always)] - pub fn add_relation( + pub fn insert_relation( self, origin: impl Entity, relation: R, @@ -622,7 +622,7 @@ impl FlowWorld { // Safety: world reference does not escape this scope. let world = unsafe { self.get() }; - world.add_relation(origin, relation, target) + world.insert_relation(origin, relation, target) } /// Removes relation between two entities in the [`FlowWorld`]. diff --git a/src/relation/components.rs b/src/relation/components.rs new file mode 100644 index 0000000..0fdaf8e --- /dev/null +++ b/src/relation/components.rs @@ -0,0 +1,374 @@ +use alloc::vec::Vec; +use core::{marker::PhantomData, mem::ManuallyDrop}; +use smallvec::SmallVec; + +use crate::{ + action::LocalActionEncoder, + component::{Component, ComponentBorrow}, + entity::EntityId, +}; + +use super::Relation; + +pub(crate) union OriginComponent { + exclusive: ManuallyDrop<(EntityId, R)>, + non_exclusive: ManuallyDrop>, +} + +impl Drop for OriginComponent +where + R: Relation, +{ + fn drop(&mut self) { + match R::EXCLUSIVE { + false => unsafe { ManuallyDrop::drop(&mut self.non_exclusive) }, + true => unsafe { ManuallyDrop::drop(&mut self.exclusive) }, + } + } +} + +impl OriginComponent +where + R: Relation, +{ + /// Called when new relation is added to an entity. + #[must_use] + pub fn new_relation(target: EntityId, relation: R) -> Self { + match R::EXCLUSIVE { + false => OriginComponent { + non_exclusive: ManuallyDrop::new(vec![(target, relation)]), + }, + true => OriginComponent { + exclusive: ManuallyDrop::new((target, relation)), + }, + } + } + + /// Called when new relation is added to an entity that already has relation of this type. + pub fn insert_relation( + &mut self, + origin: EntityId, + target: EntityId, + relation: R, + mut encoder: LocalActionEncoder, + ) -> bool { + match R::EXCLUSIVE { + false => { + let relations = unsafe { &mut *self.non_exclusive }; + for r in relations.iter_mut() { + if r.0 == target { + let call_on_drop = R::on_replace( + &mut r.1, + &relation, + origin, + target, + target, + encoder.reborrow(), + ); + if call_on_drop { + R::on_drop(r.1, origin, r.0, encoder.reborrow()); + } + + r.1 = relation; + return false; + } + } + relations.push((target, relation)); + return true; + } + true => { + let r = unsafe { &mut *self.exclusive }; + + let call_on_drop = + R::on_replace(&mut r.1, &relation, origin, r.0, target, encoder.reborrow()); + if call_on_drop { + R::on_drop(r.1, origin, r.0, encoder.reborrow()); + } + + if r.0 != target { + if R::SYMMETRIC { + if r.0 != origin { + Self::on_target_drop(core::iter::once(r.0), origin, encoder); + } + } else { + TargetComponent::::on_origin_drop( + origin, + core::iter::once(r.0), + encoder, + ); + } + r.0 = target; + return true; + } else { + return false; + } + } + } + } + + /// Called when relation is removed from an entity. + /// This won't trigger any hooks. + pub fn remove_relation( + &mut self, + origin: EntityId, + target: EntityId, + mut encoder: LocalActionEncoder, + ) -> Option { + match R::EXCLUSIVE { + false => { + let relations = unsafe { &mut *self.non_exclusive }; + for idx in 0..relations.len() { + if relations[idx].0 == target { + let r = relations.swap_remove(idx); + if relations.is_empty() { + encoder.drop::(origin); + } + return Some(r.1); + } + } + None + } + true => { + let r = unsafe { &mut *self.exclusive }; + if r.0 == target { + encoder.drop::(origin); + return Some(r.1); + } + None + } + } + } + + /// Called by target relation component when it is dropped or replaced. + fn on_target_drop( + origins: impl Iterator, + target: EntityId, + mut encoder: LocalActionEncoder, + ) { + if R::EXCLUSIVE { + if R::OWNED { + encoder.despawn_batch(origins); + } else { + encoder.drop_batch::(origins); + } + } else { + let origins = origins.collect::>(); + + encoder.closure(move |world| { + for origin in origins { + let Ok(mut origin) = world.entity(origin) else { + continue; + }; + + let Some(comp) = origin.get_mut::<&mut Self>() else { + continue; + }; + + let targets = unsafe { &mut *comp.non_exclusive }; + + for idx in 0..targets.len() { + if targets[idx].0 == target { + targets.swap_remove(idx); + break; + } + } + + if targets.is_empty() { + if R::OWNED { + origin.despawn(); + } else { + origin.drop::(); + } + } + } + }); + } + } + + #[must_use] + pub fn targets(&self) -> &[(EntityId, R)] { + match R::EXCLUSIVE { + false => unsafe { &*self.non_exclusive }, + true => core::slice::from_ref(unsafe { &*self.exclusive }), + } + } + + #[must_use] + pub fn targets_mut(&mut self) -> &mut [(EntityId, R)] { + match R::EXCLUSIVE { + false => unsafe { &mut *self.non_exclusive }, + true => core::slice::from_mut(unsafe { &mut *self.exclusive }), + } + } +} + +impl Component for OriginComponent +where + R: Relation, +{ + #[inline(always)] + fn on_drop(&mut self, origin: EntityId, mut encoder: LocalActionEncoder) { + if !self.targets().is_empty() { + R::on_origin_despawn(origin, self.targets(), encoder.reborrow()); + + if R::SYMMETRIC { + Self::on_target_drop( + self.targets().iter().map(|r| r.0), + origin, + encoder.reborrow(), + ); + } else { + TargetComponent::::on_origin_drop( + origin, + self.targets().iter().map(|r| r.0), + encoder.reborrow(), + ); + } + } + } + + #[inline(always)] + fn on_replace( + &mut self, + _value: &Self, + _origin: EntityId, + _encoder: LocalActionEncoder, + ) -> bool { + unimplemented!("This method is not intended to be called"); + } + + #[inline(always)] + #[must_use] + fn borrows() -> Vec { + Vec::new() + } +} + +/// Component that is added to target entity of the non-symmetric relation. +#[repr(transparent)] +pub(crate) struct TargetComponent { + origins: Vec<(EntityId, R)>, + relation: PhantomData R>, +} + +impl TargetComponent +where + R: Relation, +{ + #[must_use] + pub fn new(origin: EntityId, relation: R) -> Self { + debug_assert!(!R::SYMMETRIC); + + TargetComponent { + origins: vec![(origin, relation)], + relation: PhantomData, + } + } + + pub fn add(&mut self, origin: EntityId, relation: R) { + debug_assert!(!R::SYMMETRIC); + debug_assert!(self.origins.iter().all(|r| r.0 != origin)); + self.origins.push((origin, relation)); + } + + /// Called when relation is removed from an entity. + /// This won't trigger any hooks. + pub fn remove_relation( + &mut self, + origin: EntityId, + target: EntityId, + mut encoder: LocalActionEncoder, + ) { + debug_assert!(!R::SYMMETRIC); + for idx in 0..self.origins.len() { + if self.origins[idx].0 == origin { + self.origins.swap_remove(idx); + if self.origins.is_empty() { + encoder.drop::(target); + } + return; + } + } + } + + /// Called when relation is removed from origin entity. + /// Or origin entity is dropped. + fn on_origin_drop( + origin: EntityId, + targets: impl Iterator, + mut encoder: LocalActionEncoder, + ) { + debug_assert!(!R::SYMMETRIC); + let targets = targets.collect::>(); + + encoder.closure(move |world| { + for target in targets { + let Ok(mut target) = world.entity(target) else { + return; + }; + let Some(comp) = target.get_mut::<&mut Self>() else { + return; + }; + + for idx in 0..comp.origins.len() { + if comp.origins[idx].0 == origin { + comp.origins.swap_remove(idx); + break; + } + } + + if comp.origins.is_empty() { + target.drop::(); + } + } + }) + } + + pub fn origins(&self) -> &[(EntityId, R)] { + debug_assert!(!R::SYMMETRIC); + &self.origins + } + + pub fn origins_mut(&mut self) -> &mut [(EntityId, R)] { + debug_assert!(!R::SYMMETRIC); + &mut self.origins + } +} + +impl Component for TargetComponent +where + R: Relation, +{ + #[inline(always)] + fn on_drop(&mut self, target: EntityId, mut encoder: LocalActionEncoder) { + debug_assert!(!R::SYMMETRIC); + + if !self.origins.is_empty() { + R::on_target_despawn(&self.origins, target, encoder.reborrow()); + + OriginComponent::::on_target_drop( + self.origins.iter().map(|r| r.0), + target, + encoder.reborrow(), + ); + } + } + + #[inline(always)] + fn on_replace( + &mut self, + _value: &Self, + _entity: EntityId, + _encoder: LocalActionEncoder, + ) -> bool { + debug_assert!(!R::SYMMETRIC); + unimplemented!("This method is not intended to be called"); + } + + #[inline(always)] + #[must_use] + fn borrows() -> Vec { + debug_assert!(!R::SYMMETRIC); + Vec::new() + } +} diff --git a/src/relation/mod.rs b/src/relation/mod.rs index c00514a..2a36119 100644 --- a/src/relation/mod.rs +++ b/src/relation/mod.rs @@ -2,28 +2,26 @@ //! //! [`Component`]: crate::component::Component -use alloc::{vec, vec::Vec}; -use core::{marker::PhantomData, mem::ManuallyDrop}; - -use crate::{ - action::LocalActionEncoder, - component::{Component, ComponentBorrow}, - entity::EntityId, -}; +use crate::{action::LocalActionEncoder, entity::EntityId}; pub use edict_proc::Relation; pub use self::{ child_of::ChildOf, query::{ - FetchFilterRelatedBy, FetchRelated, FetchRelatesExclusiveRead, FetchRelatesExclusiveWrite, - FetchRelatesRead, FetchRelatesToRead, FetchRelatesToWrite, FetchRelatesWrite, - FilterFetchRelatesTo, FilterRelated, FilterRelatedBy, FilterRelates, FilterRelatesTo, - Related, Relates, RelatesExclusive, RelatesReadIter, RelatesTo, RelatesWriteIter, + FetchFilterRelatedBy, FetchRelatedRead, FetchRelatedWith, FetchRelatedWrite, + FetchRelatesExclusiveRead, FetchRelatesExclusiveWith, FetchRelatesExclusiveWrite, + FetchRelatesRead, FetchRelatesToRead, FetchRelatesToWrite, FetchRelatesWith, + FetchRelatesWrite, FilterFetchRelatesTo, FilterRelated, FilterRelatedBy, FilterRelates, + FilterRelatesTo, Related, Relates, RelatesExclusive, RelatesTo, RelationIter, + RelationReadIter, RelationWriteIter, }, }; +pub(crate) use self::components::{OriginComponent, TargetComponent}; + mod child_of; +mod components; mod query; /// Trait that must be implemented for types to be @@ -49,34 +47,31 @@ pub trait Relation: Copy + 'static { /// /// Non-exclusive relations is replaced only if re-added /// with same target. + /// + /// When using `#[derive(Relation)]` add `#[edict(exclusive)]` attribute to set this to true. const EXCLUSIVE: bool = false; /// If `true` then when relation is added to an entity /// it is also added to the target in reverse direction. + /// + /// When using `#[derive(Relation)]` add `#[edict(symmetric)]` attribute to set this to true. const SYMMETRIC: bool = false; /// If `true` then origin entity in relation is "owned" by the target. /// This means that when last target is dropped, entity is despawned. + /// + /// When using `#[derive(Relation)]` add `#[edict(owned)]` attribute to set this to true. const OWNED: bool = false; /// Returns name of the relation type. /// - /// Can be overriden to provide custom name. + /// Can be overridden to provide custom name. #[inline(always)] #[must_use] fn name() -> &'static str { core::any::type_name::() } - /// Method that is called when relation is dropped on origin entity. - /// Does nothing by default. - #[inline(always)] - fn on_drop(&mut self, origin: EntityId, target: EntityId, encoder: LocalActionEncoder) { - let _ = origin; - let _ = target; - let _ = encoder; - } - /// Method that is called when relation is re-inserted. /// For non-exclusive relations this happens when relation is re-inserted with the same /// origin-target entity pair. @@ -88,366 +83,66 @@ pub trait Relation: Copy + 'static { /// Does nothing by default and returns `true`, causing `on_drop` to be called. #[inline(always)] fn on_replace( - &mut self, - value: &Self, + old_value: &mut Self, + new_value: &Self, origin: EntityId, - target: EntityId, + old_target: EntityId, new_target: EntityId, encoder: LocalActionEncoder, ) -> bool { - let _ = value; + let _ = old_value; + let _ = new_value; let _ = origin; - let _ = target; + let _ = old_target; let _ = new_target; let _ = encoder; true } - /// Method that is called when target entity of the relation is dropped. - /// - /// Does nothing by default. + /// Hook that is called when relation is dropped + /// via [`World::drop_relation`] or similar method + /// or is replaced and [`Relation::on_replace`] returns `true`. #[inline(always)] - fn on_target_drop(origin: EntityId, target: EntityId, encoder: LocalActionEncoder) { + fn on_drop(self, origin: EntityId, target: EntityId, encoder: LocalActionEncoder) { let _ = origin; let _ = target; let _ = encoder; } -} - -/// Sub-trait for exclusive relations. -/// It should be implemented for relations that specify `EXCLUSIVE = true`, -/// to enable use of `RelatesExclusive` query. -/// Implementing it for relation with `EXCLUSIVE = false` will cause -/// compilation error or runtime panic. -/// -/// `Relation` derive macro implements this trait automatically. -pub trait ExclusiveRelation: Relation { - #[doc(hidden)] - const ASSERT_EXCLUSIVE: () = assert!(Self::EXCLUSIVE); -} - -pub(crate) struct RelationTarget { - pub target: EntityId, - pub relation: R, -} - -pub(crate) union OriginComponent { - exclusive: ManuallyDrop>, - non_exclusive: ManuallyDrop>>, -} - -impl Drop for OriginComponent -where - R: Relation, -{ - fn drop(&mut self) { - match R::EXCLUSIVE { - false => unsafe { ManuallyDrop::drop(&mut self.non_exclusive) }, - true => unsafe { ManuallyDrop::drop(&mut self.exclusive) }, - } - } -} - -impl OriginComponent -where - R: Relation, -{ - /// Called when new relation is added to an entity. - #[must_use] - pub fn new_relation(target: EntityId, relation: R) -> Self { - match R::EXCLUSIVE { - false => OriginComponent { - non_exclusive: ManuallyDrop::new(vec![RelationTarget { target, relation }]), - }, - true => OriginComponent { - exclusive: ManuallyDrop::new(RelationTarget { target, relation }), - }, - } - } - /// Called when new relation is added to an entity that already has relation of this type. - pub fn add_relation( - &mut self, + /// Hook that is called when origin is despawned. + #[inline(always)] + fn on_origin_despawn( origin: EntityId, - target: EntityId, - relation: R, + targets: &[(EntityId, Self)], encoder: LocalActionEncoder, ) { - match R::EXCLUSIVE { - false => { - let relations = unsafe { &mut *self.non_exclusive }; - for r in relations.iter_mut() { - if r.target == target { - Self::set_one(&mut r.relation, relation, origin, target, target, encoder); - return; - } - } - relations.push(RelationTarget { target, relation }); - } - true => { - let r = unsafe { &mut *self.exclusive }; - Self::set_one(&mut r.relation, relation, origin, r.target, target, encoder); - r.target = target; - } - } - } - - /// Called when relation is removed from an entity. - /// This won't trigger any hooks. - pub fn remove_relation( - &mut self, - origin: EntityId, - target: EntityId, - mut encoder: LocalActionEncoder, - ) -> Option { - match R::EXCLUSIVE { - false => { - let relations = unsafe { &mut *self.non_exclusive }; - for idx in 0..relations.len() { - if relations[idx].target == target { - let r = relations.swap_remove(idx); - if relations.is_empty() { - encoder.drop::(origin); - } - return Some(r.relation); - } - } - None - } - true => { - let r = unsafe { &mut *self.exclusive }; - if r.target == target { - encoder.drop::(origin); - return Some(r.relation); - } - None - } - } - } - - /// Called by target relation component when it is dropped or replaced. - fn on_target_drop(origin: EntityId, target: EntityId, mut encoder: LocalActionEncoder) { - if R::EXCLUSIVE { - if R::OWNED { - encoder.despawn(origin); - } else { - encoder.drop::(origin); - } - } else { - encoder.closure(move |world| { - let Ok(mut origin) = world.entity(origin) else { - return; - }; - - let Some(comp) = origin.get_mut::<&mut Self>() else { - return; - }; - - let origins = unsafe { &mut *comp.non_exclusive }; - - for idx in 0..origins.len() { - if origins[idx].target == target { - origins.swap_remove(idx); - break; - } - } - - if origins.is_empty() { - if R::OWNED { - origin.despawn(); - } else { - origin.drop::(); - } - } - }); - } - } - - #[must_use] - pub fn relations(&self) -> &[RelationTarget] { - match R::EXCLUSIVE { - false => unsafe { &*self.non_exclusive }, - true => core::slice::from_ref(unsafe { &*self.exclusive }), - } - } - - #[must_use] - pub fn relations_mut(&mut self) -> &mut [RelationTarget] { - match R::EXCLUSIVE { - false => unsafe { &mut *self.non_exclusive }, - true => core::slice::from_mut(unsafe { &mut *self.exclusive }), - } - } - - fn drop_one( - relation: &mut R, - origin: EntityId, - target: EntityId, - mut encoder: LocalActionEncoder, - ) { - relation.on_drop(origin, target, encoder.reborrow()); - if R::SYMMETRIC { - if target != origin { - Self::on_target_drop(target, origin, encoder); - } - } else { - TargetComponent::::on_origin_drop(origin, target, encoder) - } - } - - fn set_one( - relation: &mut R, - new_relation: R, - origin: EntityId, - target: EntityId, - new_target: EntityId, - mut encoder: LocalActionEncoder, - ) { - let on_replace = relation.on_replace( - &new_relation, - origin, - target, - new_target, - encoder.reborrow(), - ); - if on_replace { - relation.on_drop(origin, target, encoder.reborrow()); - } - if new_target != target { - if R::SYMMETRIC { - if target != origin { - Self::on_target_drop(target, origin, encoder); - } - } else { - TargetComponent::::on_origin_drop(origin, target, encoder) - } - } - *relation = new_relation; - } -} - -impl Component for OriginComponent -where - R: Relation, -{ - #[inline(always)] - fn on_drop(&mut self, origin: EntityId, mut encoder: LocalActionEncoder) { - for r in self.relations_mut() { - Self::drop_one(&mut r.relation, origin, r.target, encoder.reborrow()); - } - } - - #[inline(always)] - fn on_replace( - &mut self, - _value: &Self, - _origin: EntityId, - _encoder: LocalActionEncoder, - ) -> bool { - unimplemented!("This method is not intended to be called"); + let _ = origin; + let _ = targets; + let _ = encoder; } + /// Hook that is called when target is despawned. #[inline(always)] - #[must_use] - fn borrows() -> Vec { - Vec::new() - } -} - -/// Component that is added to target entity of the non-symmetric relation. -pub(crate) struct TargetComponent { - origins: Vec, - relation: PhantomData R>, -} - -impl TargetComponent -where - R: Relation, -{ - #[must_use] - pub(crate) fn new(origin: EntityId) -> Self { - debug_assert!(!R::SYMMETRIC); - - TargetComponent { - origins: vec![origin], - relation: PhantomData, - } - } - - pub(crate) fn add(&mut self, origin: EntityId) { - debug_assert!(!self.origins.contains(&origin)); - self.origins.push(origin); - } - - /// Called when relation is removed from an entity. - /// This won't trigger any hooks. - pub fn remove_relation( - &mut self, - origin: EntityId, + fn on_target_despawn( + origins: &[(EntityId, Self)], target: EntityId, - mut encoder: LocalActionEncoder, + encoder: LocalActionEncoder, ) { - for idx in 0..self.origins.len() { - if self.origins[idx] == origin { - self.origins.swap_remove(idx); - if self.origins.is_empty() { - encoder.drop::(target); - } - } - } - } - - /// Called when relation is removed from origin entity. - /// Or origin entity is dropped. - fn on_origin_drop(origin: EntityId, target: EntityId, mut encoder: LocalActionEncoder) { - encoder.closure(move |world| { - let Ok(mut target) = world.entity(target) else { - return; - }; - let Some(comp) = target.get_mut::<&mut Self>() else { - return; - }; - - for idx in 0..comp.origins.len() { - if comp.origins[idx] == origin { - comp.origins.swap_remove(idx); - break; - } - } - - if comp.origins.is_empty() { - target.drop::(); - } - }) + let _ = origins; + let _ = target; + let _ = encoder; } } -impl Component for TargetComponent -where - R: Relation, -{ - #[inline(always)] - fn on_drop(&mut self, target: EntityId, mut encoder: LocalActionEncoder) { - for &origin in &self.origins { - R::on_target_drop(origin, target, encoder.reborrow()); - OriginComponent::::on_target_drop(origin, target, encoder.reborrow()); - } - } - - #[inline(always)] - fn on_replace( - &mut self, - _value: &Self, - _entity: EntityId, - _encoder: LocalActionEncoder, - ) -> bool { - unimplemented!("This method is not intended to be called"); - } - - #[inline(always)] - #[must_use] - fn borrows() -> Vec { - Vec::new() - } +/// Sub-trait for exclusive relations. +/// It should be implemented for relations that specify `EXCLUSIVE = true`, +/// to enable use of `RelatesExclusive` query. +/// Implementing it for relation with `EXCLUSIVE = false` will cause +/// compilation error or runtime panic. +/// +/// `Relation` derive macro implements this trait automatically. +pub trait ExclusiveRelation: Relation { + #[doc(hidden)] + const ASSERT_EXCLUSIVE: () = assert!(Self::EXCLUSIVE); } diff --git a/src/relation/query/filter_related_by.rs b/src/relation/query/filter_related_by.rs index a33de8e..746a425 100644 --- a/src/relation/query/filter_related_by.rs +++ b/src/relation/query/filter_related_by.rs @@ -43,9 +43,9 @@ where .add(idx as usize) }; origin_component - .relations() + .targets() .iter() - .any(|rt| rt.target == self.origin) + .any(|r| r.0 == self.origin) } else { let target_component = unsafe { &*self @@ -54,7 +54,10 @@ where .as_ptr() .add(idx as usize) }; - target_component.origins.contains(&self.origin) + target_component + .origins() + .iter() + .any(|r| r.0 == self.origin) } } diff --git a/src/relation/query/filter_relates_to.rs b/src/relation/query/filter_relates_to.rs index 0e8c1c2..f522d5d 100644 --- a/src/relation/query/filter_relates_to.rs +++ b/src/relation/query/filter_relates_to.rs @@ -36,9 +36,9 @@ where unsafe fn visit_item(&mut self, idx: u32) -> bool { let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; origin_component - .relations() + .targets() .iter() - .any(|origin| origin.target == self.target) + .any(|origin| origin.0 == self.target) } #[inline(always)] diff --git a/src/relation/query/iter.rs b/src/relation/query/iter.rs new file mode 100644 index 0000000..cfe6332 --- /dev/null +++ b/src/relation/query/iter.rs @@ -0,0 +1,238 @@ +use crate::entity::{EntityBound, EntityId}; + +/// Iterator over relations of a given type on one entity. +#[derive(Clone)] +pub struct RelationIter<'a, R> { + iter: core::slice::Iter<'a, (EntityId, R)>, +} + +impl<'a, R> RelationIter<'a, R> { + /// Creates a new iterator over relations of a given type on one entity. + #[inline(always)] + pub fn new(relations: &'a [(EntityId, R)]) -> Self { + RelationIter { + iter: relations.iter(), + } + } +} + +impl<'a, R> Iterator for RelationIter<'a, R> { + type Item = EntityBound<'a>; + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline(always)] + fn next(&mut self) -> Option> { + let origin = self.iter.next()?; + Some(EntityBound::new(origin.0)) + } + + #[inline(always)] + fn nth(&mut self, n: usize) -> Option> { + let origin = self.iter.nth(n)?; + Some(EntityBound::new(origin.0)) + } + + #[inline(always)] + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter + .fold(init, |acc, origin| f(acc, EntityBound::new(origin.0))) + } +} + +impl<'a, R> DoubleEndedIterator for RelationIter<'a, R> { + #[inline(always)] + fn next_back(&mut self) -> Option> { + let origin = self.iter.next_back()?; + Some(EntityBound::new(origin.0)) + } + + #[inline(always)] + fn nth_back(&mut self, n: usize) -> Option> { + let origin = self.iter.nth_back(n)?; + Some(EntityBound::new(origin.0)) + } + + #[inline(always)] + fn rfold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter + .rfold(init, |acc, origin| f(acc, EntityBound::new(origin.0))) + } +} + +impl<'a, R> ExactSizeIterator for RelationIter<'a, R> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +/// Iterator over relations of a given type on one entity. +#[derive(Clone)] +pub struct RelationReadIter<'a, R> { + iter: core::slice::Iter<'a, (EntityId, R)>, +} + +impl<'a, R> RelationReadIter<'a, R> { + /// Creates a new iterator over relations of a given type on one entity. + #[inline(always)] + pub fn new(relations: &'a [(EntityId, R)]) -> Self { + RelationReadIter { + iter: relations.iter(), + } + } +} + +impl<'a, R> Iterator for RelationReadIter<'a, R> { + type Item = (&'a R, EntityBound<'a>); + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline(always)] + fn next(&mut self) -> Option<(&'a R, EntityBound<'a>)> { + let origin = self.iter.next()?; + Some((&origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn nth(&mut self, n: usize) -> Option<(&'a R, EntityBound<'a>)> { + let origin = self.iter.nth(n)?; + Some((&origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter.fold(init, |acc, origin| { + f(acc, (&origin.1, EntityBound::new(origin.0))) + }) + } +} + +impl<'a, R> DoubleEndedIterator for RelationReadIter<'a, R> { + #[inline(always)] + fn next_back(&mut self) -> Option<(&'a R, EntityBound<'a>)> { + let origin = self.iter.next_back()?; + Some((&origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn nth_back(&mut self, n: usize) -> Option<(&'a R, EntityBound<'a>)> { + let origin = self.iter.nth_back(n)?; + Some((&origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn rfold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, |acc, origin| { + f(acc, (&origin.1, EntityBound::new(origin.0))) + }) + } +} + +impl<'a, R> ExactSizeIterator for RelationReadIter<'a, R> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} + +/// Iterator over relations of a given type on one entity. +pub struct RelationWriteIter<'a, R> { + iter: core::slice::IterMut<'a, (EntityId, R)>, +} + +impl<'a, R> RelationWriteIter<'a, R> { + /// Creates a new iterator over relations of a given type on one entity. + #[inline(always)] + pub fn new(relations: &'a mut [(EntityId, R)]) -> Self { + RelationWriteIter { + iter: relations.iter_mut(), + } + } +} + +impl<'a, R> Iterator for RelationWriteIter<'a, R> { + type Item = (&'a mut R, EntityBound<'a>); + + #[inline(always)] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline(always)] + fn next(&mut self) -> Option<(&'a mut R, EntityBound<'a>)> { + let origin = self.iter.next()?; + Some((&mut origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn nth(&mut self, n: usize) -> Option<(&'a mut R, EntityBound<'a>)> { + let origin = self.iter.nth(n)?; + Some((&mut origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter.fold(init, |acc, origin| { + f(acc, (&mut origin.1, EntityBound::new(origin.0))) + }) + } +} + +impl<'a, R> DoubleEndedIterator for RelationWriteIter<'a, R> { + #[inline(always)] + fn next_back(&mut self) -> Option<(&'a mut R, EntityBound<'a>)> { + let origin = self.iter.next_back()?; + Some((&mut origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn nth_back(&mut self, n: usize) -> Option<(&'a mut R, EntityBound<'a>)> { + let origin = self.iter.nth_back(n)?; + Some((&mut origin.1, EntityBound::new(origin.0))) + } + + #[inline(always)] + fn rfold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter.rfold(init, |acc, origin| { + f(acc, (&mut origin.1, EntityBound::new(origin.0))) + }) + } +} + +impl<'a, R> ExactSizeIterator for RelationWriteIter<'a, R> { + #[inline(always)] + fn len(&self) -> usize { + self.iter.len() + } +} diff --git a/src/relation/query/mod.rs b/src/relation/query/mod.rs index d9c3c25..9a8bfe6 100644 --- a/src/relation/query/mod.rs +++ b/src/relation/query/mod.rs @@ -31,6 +31,7 @@ mod filter_related; mod filter_related_by; mod filter_relates; mod filter_relates_to; +mod iter; mod related; mod relates; mod relates_exclusive; @@ -41,8 +42,12 @@ pub use self::{ filter_related_by::{FetchFilterRelatedBy, FilterRelatedBy}, filter_relates::FilterRelates, filter_relates_to::{FilterFetchRelatesTo, FilterRelatesTo}, - related::{FetchRelated, Related}, - relates::{FetchRelatesRead, FetchRelatesWrite, Relates, RelatesReadIter, RelatesWriteIter}, - relates_exclusive::{FetchRelatesExclusiveRead, FetchRelatesExclusiveWrite, RelatesExclusive}, + iter::{RelationIter, RelationReadIter, RelationWriteIter}, + related::{FetchRelatedRead, FetchRelatedWith, FetchRelatedWrite, Related}, + relates::{FetchRelatesRead, FetchRelatesWith, FetchRelatesWrite, Relates}, + relates_exclusive::{ + FetchRelatesExclusiveRead, FetchRelatesExclusiveWith, FetchRelatesExclusiveWrite, + RelatesExclusive, + }, relates_to::{FetchRelatesToRead, FetchRelatesToWrite, RelatesTo}, }; diff --git a/src/relation/query/related.rs b/src/relation/query/related.rs index 3f955a3..69b92c8 100644 --- a/src/relation/query/related.rs +++ b/src/relation/query/related.rs @@ -3,16 +3,19 @@ use core::{any::TypeId, marker::PhantomData, ptr::NonNull}; use crate::{ archetype::Archetype, component::ComponentInfo, - entity::EntityBound, + entity::EntityId, epoch::EpochId, query::{ - AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, SendQuery, WriteAlias, + AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, Read, SendQuery, With, + Write, WriteAlias, }, relation::{OriginComponent, Relation, TargetComponent}, system::QueryArg, type_id, Access, }; +use super::{RelationIter, RelationReadIter, RelationWriteIter}; + marker_type! { /// Query for target of relation. /// @@ -20,14 +23,14 @@ marker_type! { pub struct Related; } -impl AsQuery for Related +impl AsQuery for Related> where R: Relation, { type Query = Self; } -impl IntoQuery for Related +impl IntoQuery for Related> where R: Relation, { @@ -37,7 +40,7 @@ where } } -impl DefaultQuery for Related +impl DefaultQuery for Related> where R: Relation, { @@ -47,7 +50,139 @@ where } } -impl QueryArg for Related +/// Fetch type for [`Related>`] +pub struct FetchRelatedWith<'a, R> { + ptr: NonNull, + marker: PhantomData<&'a (EntityId, R)>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatedWith<'a, R> +where + R: Relation, +{ + type Item = RelationIter<'a, R>; + + #[inline(always)] + fn dangling() -> Self { + FetchRelatedWith { + ptr: NonNull::dangling(), + marker: PhantomData, + } + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> RelationIter<'a, R> { + if R::SYMMETRIC { + let component = unsafe { + &*self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationIter::new(component.targets()) + } else { + let component = unsafe { + &*self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationIter::new(component.origins()) + } + } +} + +unsafe impl Query for Related> +where + R: Relation, +{ + type Item<'a> = RelationIter<'a, R>; + type Fetch<'a> = FetchRelatedWith<'a, R>; + + const MUTABLE: bool = false; + + #[inline(always)] + fn component_access(&self, comp: &ComponentInfo) -> Result, WriteAlias> { + if R::SYMMETRIC { + if comp.id() == type_id::>() { + Ok(Some(Access::Read)) + } else { + Ok(None) + } + } else { + if comp.id() == type_id::>() { + Ok(Some(Access::Read)) + } else { + Ok(None) + } + } + } + + #[inline(always)] + fn visit_archetype(&self, archetype: &Archetype) -> bool { + if R::SYMMETRIC { + archetype.has_component(type_id::>()) + } else { + archetype.has_component(type_id::>()) + } + } + + #[inline(always)] + unsafe fn access_archetype(&self, _archetype: &Archetype, mut f: impl FnMut(TypeId, Access)) { + if R::SYMMETRIC { + f(type_id::>(), Access::Read) + } else { + f(type_id::>(), Access::Read) + } + } + + #[inline(always)] + unsafe fn fetch<'a>( + &self, + _arch_idx: u32, + archetype: &'a Archetype, + _epoch: EpochId, + ) -> FetchRelatedWith<'a, R> { + if R::SYMMETRIC { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data() }; + + FetchRelatedWith { + ptr: data.ptr.cast(), + marker: PhantomData, + } + } else { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data() }; + + FetchRelatedWith { + ptr: data.ptr.cast(), + marker: PhantomData, + } + } + } +} + +unsafe impl ImmutableQuery for Related> where R: Relation {} +unsafe impl SendQuery for Related> where R: Relation {} + +impl QueryArg for Related> where R: Relation, { @@ -57,39 +192,102 @@ where } } +impl AsQuery for Related<&R> +where + R: Relation, +{ + type Query = Related>; +} + +impl DefaultQuery for Related<&R> +where + R: Relation, +{ + #[inline(always)] + fn default_query() -> Related> { + Related + } +} + +impl AsQuery for Related> +where + R: Relation, +{ + type Query = Self; +} + +impl IntoQuery for Related> +where + R: Relation, +{ + #[inline(always)] + fn into_query(self) -> Self { + self + } +} + +impl DefaultQuery for Related> +where + R: Relation, +{ + #[inline(always)] + fn default_query() -> Self { + Related + } +} + /// Fetch type for [`Related`] -pub struct FetchRelated<'a, R> { - ptr: NonNull>, - marker: PhantomData<&'a TargetComponent>, +pub struct FetchRelatedRead<'a, R> { + ptr: NonNull, + marker: PhantomData<&'a (EntityId, R)>, } -unsafe impl<'a, R> Fetch<'a> for FetchRelated<'a, R> +unsafe impl<'a, R> Fetch<'a> for FetchRelatedRead<'a, R> where R: Relation, { - type Item = &'a [EntityBound<'a>]; + type Item = RelationReadIter<'a, R>; #[inline(always)] fn dangling() -> Self { - FetchRelated { + FetchRelatedRead { ptr: NonNull::dangling(), marker: PhantomData, } } #[inline(always)] - unsafe fn get_item(&mut self, idx: u32) -> &'a [EntityBound<'a>] { - let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; - EntityBound::wrap_slice(&component.origins[..]) + unsafe fn get_item(&mut self, idx: u32) -> RelationReadIter<'a, R> { + if R::SYMMETRIC { + let component = unsafe { + &*self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationReadIter::new(component.targets()) + } else { + let component = unsafe { + &*self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationReadIter::new(component.origins()) + } } } -unsafe impl Query for Related +unsafe impl Query for Related> where R: Relation, { - type Item<'a> = &'a [EntityBound<'a>]; - type Fetch<'a> = FetchRelated<'a, R>; + type Item<'a> = RelationReadIter<'a, R>; + type Fetch<'a> = FetchRelatedRead<'a, R>; const MUTABLE: bool = false; @@ -113,7 +311,11 @@ where #[inline(always)] unsafe fn access_archetype(&self, _archetype: &Archetype, mut f: impl FnMut(TypeId, Access)) { - f(type_id::>(), Access::Read) + if R::SYMMETRIC { + f(type_id::>(), Access::Read) + } else { + f(type_id::>(), Access::Read) + } } #[inline(always)] @@ -122,22 +324,249 @@ where _arch_idx: u32, archetype: &'a Archetype, _epoch: EpochId, - ) -> FetchRelated<'a, R> { - let component = unsafe { - archetype - .component(type_id::>()) - .unwrap_unchecked() - }; - debug_assert_eq!(component.id(), type_id::>()); - - let data = unsafe { component.data() }; - - FetchRelated { - ptr: data.ptr.cast(), + ) -> FetchRelatedRead<'a, R> { + if R::SYMMETRIC { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data() }; + + FetchRelatedRead { + ptr: data.ptr.cast(), + marker: PhantomData, + } + } else { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data() }; + + FetchRelatedRead { + ptr: data.ptr.cast(), + marker: PhantomData, + } + } + } +} + +unsafe impl ImmutableQuery for Related> where R: Relation {} +unsafe impl SendQuery for Related> where R: Relation + Sync {} + +impl QueryArg for Related> +where + R: Relation + Sync, +{ + #[inline(always)] + fn new() -> Self { + Related + } +} + +impl AsQuery for Related<&mut R> +where + R: Relation, +{ + type Query = Related>; +} + +impl DefaultQuery for Related<&mut R> +where + R: Relation, +{ + #[inline(always)] + fn default_query() -> Related> { + Related + } +} + +impl AsQuery for Related> +where + R: Relation, +{ + type Query = Self; +} + +impl IntoQuery for Related> +where + R: Relation, +{ + #[inline(always)] + fn into_query(self) -> Self { + self + } +} + +impl DefaultQuery for Related> +where + R: Relation, +{ + #[inline(always)] + fn default_query() -> Self { + Related + } +} + +/// Fetch type for [`Related`] +pub struct FetchRelatedWrite<'a, R> { + ptr: NonNull, + epoch: EpochId, + entity_epochs: NonNull, + chunk_epochs: NonNull, + marker: PhantomData<&'a (EntityId, R)>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatedWrite<'a, R> +where + R: Relation, +{ + type Item = RelationWriteIter<'a, R>; + + #[inline(always)] + fn dangling() -> Self { + FetchRelatedWrite { + ptr: NonNull::dangling(), + epoch: EpochId::start(), + entity_epochs: NonNull::dangling(), + chunk_epochs: NonNull::dangling(), marker: PhantomData, } } + + #[inline(always)] + unsafe fn touch_chunk(&mut self, chunk_idx: u32) { + let chunk_epoch = unsafe { &mut *self.chunk_epochs.as_ptr().add(chunk_idx as usize) }; + chunk_epoch.bump(self.epoch); + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> RelationWriteIter<'a, R> { + let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; + entity_epoch.bump(self.epoch); + + if R::SYMMETRIC { + let component = unsafe { + &mut *self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationWriteIter::new(component.targets_mut()) + } else { + let component = unsafe { + &mut *self + .ptr + .cast::>() + .as_ptr() + .add(idx as usize) + }; + + RelationWriteIter::new(component.origins_mut()) + } + } } -unsafe impl ImmutableQuery for Related where R: Relation {} -unsafe impl SendQuery for Related where R: Relation {} +unsafe impl Query for Related> +where + R: Relation, +{ + type Item<'a> = RelationWriteIter<'a, R>; + type Fetch<'a> = FetchRelatedWrite<'a, R>; + + const MUTABLE: bool = false; + + #[inline(always)] + fn component_access(&self, comp: &ComponentInfo) -> Result, WriteAlias> { + if comp.id() == type_id::>() { + Ok(Some(Access::Write)) + } else { + Ok(None) + } + } + + #[inline(always)] + fn visit_archetype(&self, archetype: &Archetype) -> bool { + if R::SYMMETRIC { + archetype.has_component(type_id::>()) + } else { + archetype.has_component(type_id::>()) + } + } + + #[inline(always)] + unsafe fn access_archetype(&self, _archetype: &Archetype, mut f: impl FnMut(TypeId, Access)) { + if R::SYMMETRIC { + f(type_id::>(), Access::Write) + } else { + f(type_id::>(), Access::Write) + } + } + + #[inline(always)] + unsafe fn fetch<'a>( + &self, + _arch_idx: u32, + archetype: &'a Archetype, + epoch: EpochId, + ) -> FetchRelatedWrite<'a, R> { + if R::SYMMETRIC { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data_mut() }; + data.epoch.bump(epoch); + + FetchRelatedWrite { + ptr: data.ptr.cast(), + epoch, + entity_epochs: unsafe { NonNull::new_unchecked(data.entity_epochs.as_mut_ptr()) }, + chunk_epochs: unsafe { NonNull::new_unchecked(data.chunk_epochs.as_mut_ptr()) }, + marker: PhantomData, + } + } else { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data_mut() }; + data.epoch.bump(epoch); + + FetchRelatedWrite { + ptr: data.ptr.cast(), + epoch, + entity_epochs: unsafe { NonNull::new_unchecked(data.entity_epochs.as_mut_ptr()) }, + chunk_epochs: unsafe { NonNull::new_unchecked(data.chunk_epochs.as_mut_ptr()) }, + marker: PhantomData, + } + } + } +} + +unsafe impl ImmutableQuery for Related> where R: Relation {} +unsafe impl SendQuery for Related> where R: Relation + Send {} + +impl QueryArg for Related> +where + R: Relation + Send, +{ + #[inline(always)] + fn new() -> Self { + Related + } +} diff --git a/src/relation/query/relates.rs b/src/relation/query/relates.rs index 9c43f93..89178c0 100644 --- a/src/relation/query/relates.rs +++ b/src/relation/query/relates.rs @@ -3,17 +3,18 @@ use core::{any::TypeId, marker::PhantomData, ptr::NonNull}; use crate::{ archetype::Archetype, component::ComponentInfo, - entity::{EntityBound, EntityId}, epoch::EpochId, query::{ - AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, Read, SendQuery, Write, - WriteAlias, + AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, Read, SendQuery, With, + Write, WriteAlias, }, - relation::{OriginComponent, Relation, RelationTarget}, + relation::{OriginComponent, Relation}, system::QueryArg, type_id, Access, }; +use super::{RelationIter, RelationReadIter, RelationWriteIter}; + marker_type! { /// Query for origins of relation. /// @@ -21,103 +22,123 @@ marker_type! { pub struct Relates; } -/// Iterator over relations of a given type on one entity. -#[derive(Clone)] -pub struct RelatesReadIter<'a, R> { - iter: core::slice::Iter<'a, RelationTarget>, +impl AsQuery for Relates> +where + R: Relation, +{ + type Query = Self; } -impl<'a, R> Iterator for RelatesReadIter<'a, R> { - type Item = (&'a R, EntityBound<'a>); - +impl IntoQuery for Relates> +where + R: Relation, +{ #[inline(always)] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() + fn into_query(self) -> Self::Query { + self } +} +impl DefaultQuery for Relates> +where + R: Relation, +{ #[inline(always)] - fn next(&mut self) -> Option<(&'a R, EntityBound<'a>)> { - let origin = self.iter.next()?; - Some((&origin.relation, EntityBound::new(origin.target))) + fn default_query() -> Self { + Relates } +} + +/// Fetch for the [`Relates<&R>`] query. +pub struct FetchRelatesWith<'a, R: Relation> { + ptr: NonNull>, + marker: PhantomData<&'a OriginComponent>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatesWith<'a, R> +where + R: Relation, +{ + type Item = RelationIter<'a, R>; #[inline(always)] - fn nth(&mut self, n: usize) -> Option<(&'a R, EntityBound<'a>)> { - let origin = self.iter.nth(n)?; - Some((&origin.relation, EntityBound::new(origin.target))) + fn dangling() -> Self { + FetchRelatesWith { + ptr: NonNull::dangling(), + marker: PhantomData, + } } #[inline(always)] - fn fold(self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter.fold(init, |acc, origin| { - f(acc, (&origin.relation, EntityBound::new(origin.target))) - }) + unsafe fn get_item(&mut self, idx: u32) -> RelationIter<'a, R> { + let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + + RelationIter::new(component.targets()) } } -impl<'a, R> DoubleEndedIterator for RelatesReadIter<'a, R> { +unsafe impl Query for Relates> +where + R: Relation, +{ + type Item<'a> = RelationIter<'a, R>; + type Fetch<'a> = FetchRelatesWith<'a, R>; + + const MUTABLE: bool = false; + #[inline(always)] - fn next_back(&mut self) -> Option<(&'a R, EntityBound<'a>)> { - let origin = self.iter.next_back()?; - Some((&origin.relation, EntityBound::new(origin.target))) + fn component_access(&self, comp: &ComponentInfo) -> Result, WriteAlias> { + if comp.id() == type_id::>() { + Ok(Some(Access::Read)) + } else { + Ok(None) + } } #[inline(always)] - fn nth_back(&mut self, n: usize) -> Option<(&'a R, EntityBound<'a>)> { - let origin = self.iter.nth_back(n)?; - Some((&origin.relation, EntityBound::new(origin.target))) + fn visit_archetype(&self, archetype: &Archetype) -> bool { + archetype.has_component(type_id::>()) } #[inline(always)] - fn rfold(self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter.rfold(init, |acc, origin| { - f(acc, (&origin.relation, EntityBound::new(origin.target))) - }) + unsafe fn access_archetype(&self, _archetype: &Archetype, mut f: impl FnMut(TypeId, Access)) { + f(type_id::>(), Access::Read) } -} -impl<'a, R> ExactSizeIterator for RelatesReadIter<'a, R> { #[inline(always)] - fn len(&self) -> usize { - self.iter.len() - } -} + unsafe fn fetch<'a>( + &self, + _arch_idx: u32, + archetype: &'a Archetype, + _epoch: EpochId, + ) -> FetchRelatesWith<'a, R> { + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; -/// Fetch for the [`Relates<&R>`] query. -pub struct FetchRelatesRead<'a, R: Relation> { - ptr: NonNull>, - marker: PhantomData<&'a OriginComponent>, -} + debug_assert_eq!(component.id(), type_id::>()); -unsafe impl<'a, R> Fetch<'a> for FetchRelatesRead<'a, R> -where - R: Relation, -{ - type Item = RelatesReadIter<'a, R>; + let data = unsafe { component.data() }; - #[inline(always)] - fn dangling() -> Self { - FetchRelatesRead { - ptr: NonNull::dangling(), + FetchRelatesWith { + ptr: data.ptr.cast(), marker: PhantomData, } } +} - #[inline(always)] - unsafe fn get_item(&mut self, idx: u32) -> RelatesReadIter<'a, R> { - let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; +unsafe impl ImmutableQuery for Relates> where R: Relation {} +unsafe impl SendQuery for Relates> where R: Relation {} - RelatesReadIter { - iter: origin_component.relations().iter(), - } +impl QueryArg for Relates> +where + R: Relation, +{ + #[inline(always)] + fn new() -> Self { + Relates } } @@ -165,13 +186,31 @@ where } } -impl QueryArg for Relates> +/// Fetch for the [`Relates<&R>`] query. +pub struct FetchRelatesRead<'a, R: Relation> { + ptr: NonNull>, + marker: PhantomData<&'a OriginComponent>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatesRead<'a, R> where - R: Sync + Relation, + R: Relation, { + type Item = RelationReadIter<'a, R>; + #[inline(always)] - fn new() -> Self { - Relates + fn dangling() -> Self { + FetchRelatesRead { + ptr: NonNull::dangling(), + marker: PhantomData, + } + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> RelationReadIter<'a, R> { + let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + + RelationReadIter::new(component.targets()) } } @@ -179,7 +218,7 @@ unsafe impl Query for Relates> where R: Relation, { - type Item<'a> = RelatesReadIter<'a, R>; + type Item<'a> = RelationReadIter<'a, R>; type Fetch<'a> = FetchRelatesRead<'a, R>; const MUTABLE: bool = false; @@ -230,117 +269,13 @@ where unsafe impl ImmutableQuery for Relates> where R: Relation {} unsafe impl SendQuery for Relates> where R: Relation + Sync {} -/// Iterator over relations of a given type on one entity. -pub struct RelatesWriteIter<'a, R> { - iter: core::slice::IterMut<'a, RelationTarget>, -} - -impl<'a, R> Iterator for RelatesWriteIter<'a, R> { - type Item = (&'a mut R, EntityId); - - #[inline(always)] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - - #[inline(always)] - fn next(&mut self) -> Option<(&'a mut R, EntityId)> { - let origin = self.iter.next()?; - Some((&mut origin.relation, origin.target)) - } - - #[inline(always)] - fn nth(&mut self, n: usize) -> Option<(&'a mut R, EntityId)> { - let origin = self.iter.nth(n)?; - Some((&mut origin.relation, origin.target)) - } - - #[inline(always)] - fn fold(self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter.fold(init, |acc, origin| { - f(acc, (&mut origin.relation, origin.target)) - }) - } -} - -impl<'a, R> DoubleEndedIterator for RelatesWriteIter<'a, R> { - #[inline(always)] - fn next_back(&mut self) -> Option<(&'a mut R, EntityId)> { - let origin = self.iter.next_back()?; - Some((&mut origin.relation, origin.target)) - } - - #[inline(always)] - fn nth_back(&mut self, n: usize) -> Option<(&'a mut R, EntityId)> { - let origin = self.iter.nth_back(n)?; - Some((&mut origin.relation, origin.target)) - } - - #[inline(always)] - fn rfold(self, init: B, mut f: F) -> B - where - Self: Sized, - F: FnMut(B, Self::Item) -> B, - { - self.iter.rfold(init, |acc, origin| { - f(acc, (&mut origin.relation, origin.target)) - }) - } -} - -impl<'a, R> ExactSizeIterator for RelatesWriteIter<'a, R> { - #[inline(always)] - fn len(&self) -> usize { - self.iter.len() - } -} - -/// Fetch for the [`Relates<&mut R>`] query. -pub struct FetchRelatesWrite<'a, R: Relation> { - epoch: EpochId, - ptr: NonNull>, - entity_epochs: NonNull, - chunk_epochs: NonNull, - marker: PhantomData<&'a mut OriginComponent>, -} - -unsafe impl<'a, R> Fetch<'a> for FetchRelatesWrite<'a, R> +impl QueryArg for Relates> where - R: Relation, + R: Sync + Relation, { - type Item = RelatesWriteIter<'a, R>; - - #[inline(always)] - fn dangling() -> Self { - FetchRelatesWrite { - epoch: EpochId::start(), - ptr: NonNull::dangling(), - entity_epochs: NonNull::dangling(), - chunk_epochs: NonNull::dangling(), - marker: PhantomData, - } - } - - #[inline(always)] - unsafe fn touch_chunk(&mut self, chunk_idx: u32) { - let chunk_epoch = unsafe { &mut *self.chunk_epochs.as_ptr().add(chunk_idx as usize) }; - chunk_epoch.bump(self.epoch); - } - #[inline(always)] - unsafe fn get_item(&mut self, idx: u32) -> RelatesWriteIter<'a, R> { - let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; - entity_epoch.bump(self.epoch); - - let origin_component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; - - RelatesWriteIter { - iter: origin_component.relations_mut().iter_mut(), - } + fn new() -> Self { + Relates } } @@ -388,13 +323,46 @@ where } } -impl QueryArg for Relates> +/// Fetch for the [`Relates<&mut R>`] query. +pub struct FetchRelatesWrite<'a, R: Relation> { + ptr: NonNull>, + epoch: EpochId, + entity_epochs: NonNull, + chunk_epochs: NonNull, + marker: PhantomData<&'a mut OriginComponent>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatesWrite<'a, R> where - R: Send + Relation, + R: Relation, { + type Item = RelationWriteIter<'a, R>; + #[inline(always)] - fn new() -> Self { - Relates + fn dangling() -> Self { + FetchRelatesWrite { + ptr: NonNull::dangling(), + epoch: EpochId::start(), + entity_epochs: NonNull::dangling(), + chunk_epochs: NonNull::dangling(), + marker: PhantomData, + } + } + + #[inline(always)] + unsafe fn touch_chunk(&mut self, chunk_idx: u32) { + let chunk_epoch = unsafe { &mut *self.chunk_epochs.as_ptr().add(chunk_idx as usize) }; + chunk_epoch.bump(self.epoch); + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> RelationWriteIter<'a, R> { + let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; + entity_epoch.bump(self.epoch); + + let component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; + + RelationWriteIter::new(component.targets_mut()) } } @@ -402,7 +370,7 @@ unsafe impl Query for Relates> where R: Relation, { - type Item<'a> = RelatesWriteIter<'a, R>; + type Item<'a> = RelationWriteIter<'a, R>; type Fetch<'a> = FetchRelatesWrite<'a, R>; const MUTABLE: bool = true; @@ -444,8 +412,8 @@ where data.epoch.bump(epoch); FetchRelatesWrite { - epoch, ptr: data.ptr.cast(), + epoch, entity_epochs: unsafe { NonNull::new_unchecked(data.entity_epochs.as_mut_ptr()) }, chunk_epochs: unsafe { NonNull::new_unchecked(data.chunk_epochs.as_mut_ptr()) }, marker: PhantomData, @@ -454,3 +422,13 @@ where } unsafe impl SendQuery for Relates> where R: Relation + Send {} + +impl QueryArg for Relates> +where + R: Send + Relation, +{ + #[inline(always)] + fn new() -> Self { + Relates + } +} diff --git a/src/relation/query/relates_exclusive.rs b/src/relation/query/relates_exclusive.rs index ad601f4..e21dfc4 100644 --- a/src/relation/query/relates_exclusive.rs +++ b/src/relation/query/relates_exclusive.rs @@ -3,11 +3,11 @@ use core::{any::TypeId, marker::PhantomData, ptr::NonNull}; use crate::{ archetype::Archetype, component::ComponentInfo, - entity::{EntityBound, EntityId}, + entity::EntityBound, epoch::EpochId, query::{ - AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, Read, SendQuery, Write, - WriteAlias, + AsQuery, DefaultQuery, Fetch, ImmutableQuery, IntoQuery, Query, Read, SendQuery, With, + Write, WriteAlias, }, relation::{ExclusiveRelation, OriginComponent}, system::QueryArg, @@ -21,31 +21,124 @@ marker_type! { pub struct RelatesExclusive; } +impl AsQuery for RelatesExclusive> +where + R: ExclusiveRelation, +{ + type Query = Self; +} + +impl IntoQuery for RelatesExclusive> +where + R: ExclusiveRelation, +{ + #[inline(always)] + fn into_query(self) -> Self { + self + } +} + +impl DefaultQuery for RelatesExclusive> +where + R: ExclusiveRelation, +{ + #[inline(always)] + fn default_query() -> Self { + RelatesExclusive + } +} + /// Fetch for the [`RelatesExclusive<&R>`] query. -pub struct FetchRelatesExclusiveRead<'a, R: ExclusiveRelation> { +pub struct FetchRelatesExclusiveWith<'a, R: ExclusiveRelation> { ptr: NonNull>, marker: PhantomData<&'a OriginComponent>, } -unsafe impl<'a, R> Fetch<'a> for FetchRelatesExclusiveRead<'a, R> +unsafe impl<'a, R> Fetch<'a> for FetchRelatesExclusiveWith<'a, R> where R: ExclusiveRelation, { - type Item = (&'a R, EntityBound<'a>); + type Item = EntityBound<'a>; #[inline(always)] fn dangling() -> Self { - FetchRelatesExclusiveRead { + FetchRelatesExclusiveWith { ptr: NonNull::dangling(), marker: PhantomData, } } #[inline(always)] - unsafe fn get_item(&mut self, idx: u32) -> (&'a R, EntityBound<'a>) { + unsafe fn get_item(&mut self, idx: u32) -> EntityBound<'a> { let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; - let origin = &origin_component.relations()[0]; - (&origin.relation, EntityBound::new(origin.target)) + let origin = &origin_component.targets()[0]; + EntityBound::new(origin.0) + } +} + +unsafe impl Query for RelatesExclusive> +where + R: ExclusiveRelation, +{ + type Item<'a> = EntityBound<'a>; + type Fetch<'a> = FetchRelatesExclusiveWith<'a, R>; + + const MUTABLE: bool = false; + + #[inline(always)] + fn component_access(&self, comp: &ComponentInfo) -> Result, WriteAlias> { + if comp.id() == type_id::>() { + Ok(Some(Access::Read)) + } else { + Ok(None) + } + } + + fn visit_archetype(&self, archetype: &Archetype) -> bool { + archetype.has_component(type_id::>()) + } + + #[inline(always)] + unsafe fn access_archetype(&self, _archetype: &Archetype, mut f: impl FnMut(TypeId, Access)) { + f(type_id::>(), Access::Read) + } + + #[inline(always)] + unsafe fn fetch<'a>( + &self, + _arch_idx: u32, + archetype: &'a Archetype, + _epoch: EpochId, + ) -> FetchRelatesExclusiveWith<'a, R> { + let () = R::ASSERT_EXCLUSIVE; + + let component = unsafe { + archetype + .component(type_id::>()) + .unwrap_unchecked() + }; + + debug_assert_eq!(component.id(), type_id::>()); + + let data = unsafe { component.data() }; + + FetchRelatesExclusiveWith { + ptr: data.ptr.cast(), + marker: PhantomData, + } + } +} + +unsafe impl ImmutableQuery for RelatesExclusive> where R: ExclusiveRelation {} +unsafe impl SendQuery for RelatesExclusive> where R: ExclusiveRelation {} + +impl QueryArg for RelatesExclusive> +where + R: ExclusiveRelation, +{ + #[inline(always)] + fn new() -> Self { + RelatesExclusive } } @@ -93,13 +186,31 @@ where } } -impl QueryArg for RelatesExclusive> +/// Fetch for the [`RelatesExclusive<&R>`] query. +pub struct FetchRelatesExclusiveRead<'a, R: ExclusiveRelation> { + ptr: NonNull>, + marker: PhantomData<&'a OriginComponent>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatesExclusiveRead<'a, R> where - R: Sync + ExclusiveRelation, + R: ExclusiveRelation, { + type Item = (&'a R, EntityBound<'a>); + #[inline(always)] - fn new() -> Self { - RelatesExclusive + fn dangling() -> Self { + FetchRelatesExclusiveRead { + ptr: NonNull::dangling(), + marker: PhantomData, + } + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> (&'a R, EntityBound<'a>) { + let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + let origin = &origin_component.targets()[0]; + (&origin.1, EntityBound::new(origin.0)) } } @@ -159,46 +270,13 @@ where unsafe impl ImmutableQuery for RelatesExclusive> where R: ExclusiveRelation {} unsafe impl SendQuery for RelatesExclusive> where R: ExclusiveRelation + Sync {} -/// Fetch for the [`RelatesExclusive<&mut R>`] query. -pub struct FetchRelatesExclusiveWrite<'a, R: ExclusiveRelation> { - epoch: EpochId, - ptr: NonNull>, - entity_epochs: NonNull, - chunk_epochs: NonNull, - marker: PhantomData<&'a mut OriginComponent>, -} - -unsafe impl<'a, R> Fetch<'a> for FetchRelatesExclusiveWrite<'a, R> +impl QueryArg for RelatesExclusive> where - R: ExclusiveRelation, + R: ExclusiveRelation + Sync, { - type Item = (&'a mut R, EntityId); - - #[inline(always)] - fn dangling() -> Self { - FetchRelatesExclusiveWrite { - epoch: EpochId::start(), - ptr: NonNull::dangling(), - entity_epochs: NonNull::dangling(), - chunk_epochs: NonNull::dangling(), - marker: PhantomData, - } - } - - #[inline(always)] - unsafe fn touch_chunk(&mut self, chunk_idx: u32) { - let chunk_epoch = unsafe { &mut *self.chunk_epochs.as_ptr().add(chunk_idx as usize) }; - chunk_epoch.bump(self.epoch); - } - #[inline(always)] - unsafe fn get_item(&mut self, idx: u32) -> (&'a mut R, EntityId) { - let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; - entity_epoch.bump(self.epoch); - - let origin_component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; - let origin = &mut origin_component.relations_mut()[0]; - (&mut origin.relation, origin.target) + fn new() -> Self { + RelatesExclusive } } @@ -246,13 +324,46 @@ where } } -impl QueryArg for RelatesExclusive> +/// Fetch for the [`RelatesExclusive<&mut R>`] query. +pub struct FetchRelatesExclusiveWrite<'a, R: ExclusiveRelation> { + epoch: EpochId, + ptr: NonNull>, + entity_epochs: NonNull, + chunk_epochs: NonNull, + marker: PhantomData<&'a mut OriginComponent>, +} + +unsafe impl<'a, R> Fetch<'a> for FetchRelatesExclusiveWrite<'a, R> where - R: Send + ExclusiveRelation, + R: ExclusiveRelation, { + type Item = (&'a mut R, EntityBound<'a>); + #[inline(always)] - fn new() -> Self { - RelatesExclusive + fn dangling() -> Self { + FetchRelatesExclusiveWrite { + epoch: EpochId::start(), + ptr: NonNull::dangling(), + entity_epochs: NonNull::dangling(), + chunk_epochs: NonNull::dangling(), + marker: PhantomData, + } + } + + #[inline(always)] + unsafe fn touch_chunk(&mut self, chunk_idx: u32) { + let chunk_epoch = unsafe { &mut *self.chunk_epochs.as_ptr().add(chunk_idx as usize) }; + chunk_epoch.bump(self.epoch); + } + + #[inline(always)] + unsafe fn get_item(&mut self, idx: u32) -> (&'a mut R, EntityBound<'a>) { + let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; + entity_epoch.bump(self.epoch); + + let origin_component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; + let origin = &mut origin_component.targets_mut()[0]; + (&mut origin.1, EntityBound::new(origin.0)) } } @@ -260,7 +371,7 @@ unsafe impl Query for RelatesExclusive> where R: ExclusiveRelation, { - type Item<'a> = (&'a mut R, EntityId); + type Item<'a> = (&'a mut R, EntityBound<'a>); type Fetch<'a> = FetchRelatesExclusiveWrite<'a, R>; const MUTABLE: bool = true; @@ -314,3 +425,13 @@ where } unsafe impl SendQuery for RelatesExclusive> where R: ExclusiveRelation + Send {} + +impl QueryArg for RelatesExclusive> +where + R: ExclusiveRelation + Send, +{ + #[inline(always)] + fn new() -> Self { + RelatesExclusive + } +} diff --git a/src/relation/query/relates_to.rs b/src/relation/query/relates_to.rs index a3415fd..6af4fc2 100644 --- a/src/relation/query/relates_to.rs +++ b/src/relation/query/relates_to.rs @@ -57,11 +57,11 @@ where #[inline(always)] unsafe fn visit_item(&mut self, idx: u32) -> bool { - let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; - let item_idx = origin_component - .relations() + let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + let item_idx = component + .targets() .iter() - .position(|origin| origin.target == self.target); + .position(|origin| origin.0 == self.target); match item_idx { None => false, @@ -74,8 +74,8 @@ where #[inline(always)] unsafe fn get_item(&mut self, idx: u32) -> &'a R { - let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; - &origin_component.relations()[self.item_idx].relation + let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + &component.targets()[self.item_idx].1 } } @@ -197,11 +197,11 @@ where #[inline(always)] unsafe fn visit_item(&mut self, idx: u32) -> bool { - let origin_component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; - let item_idx = origin_component - .relations() + let component = unsafe { &*self.ptr.as_ptr().add(idx as usize) }; + let item_idx = component + .targets() .iter() - .position(|origin| origin.target == self.target); + .position(|origin| origin.0 == self.target); match item_idx { None => false, @@ -217,8 +217,8 @@ where let entity_epoch = unsafe { &mut *self.entity_epochs.as_ptr().add(idx as usize) }; entity_epoch.bump(self.epoch); - let origin_component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; - &mut origin_component.relations_mut()[self.item_idx].relation + let component = unsafe { &mut *self.ptr.as_ptr().add(idx as usize) }; + &mut component.targets_mut()[self.item_idx].1 } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 92fd01c..3dd0bea 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -549,8 +549,8 @@ impl Scheduler { for info in archetype.infos() { if conflicts( - system_a.component_access(info), - system_b.component_access(info), + system_a.component_access(archetype, info), + system_b.component_access(archetype, info), ) { // Conflicts on this archetype. // Add a dependency. diff --git a/src/system/func/mod.rs b/src/system/func/mod.rs index b1836d5..836bcc9 100644 --- a/src/system/func/mod.rs +++ b/src/system/func/mod.rs @@ -155,26 +155,28 @@ macro_rules! impl_func { } #[inline(always)] - fn component_access(&self, comp: &ComponentInfo) -> Option { + fn component_access(&self, archetype: &Archetype, comp: &ComponentInfo) -> Option { let ($($a,)*) = &self.args; let mut result = None; let mut runtime_borrow = true; $( - if let Some(access) = $a.component_access(comp) { - runtime_borrow &= $a.borrows_components_at_runtime(); - result = match (result, access) { - (None, one) => Some(one), - (Some(Access::Read), Access::Read) => Some(Access::Read), - (Some(Access::Write), _) | (Some(_), Access::Write) => { - if runtime_borrow { - // All args that access this component use runtime borrow. - // Conflict will be resolved at runtime. - Some(Access::Write) - } else { - panic!("Conflicting args in system `{}`.\nA component is aliased mutably.\nIf arguments require mutable aliasing, all arguments that access a type must use runtime borrow check.\nFor example `View` type does not use runtime borrow check and should be replaced with `ViewCell`.", type_name::()); + if $a.visit_archetype(archetype) { + if let Some(access) = $a.component_access(comp) { + runtime_borrow &= $a.borrows_components_at_runtime(); + result = match (result, access) { + (None, one) => Some(one), + (Some(Access::Read), Access::Read) => Some(Access::Read), + (Some(Access::Write), _) | (Some(_), Access::Write) => { + if runtime_borrow { + // All args that access this component use runtime borrow. + // Conflict will be resolved at runtime. + Some(Access::Write) + } else { + panic!("Conflicting args in system `{}`.\nA component is aliased mutably.\nIf arguments require mutable aliasing, all arguments that access a type must use runtime borrow check.\nFor example `View` type does not use runtime borrow check and should be replaced with `ViewCell`.", type_name::()); + } } - } - }; + }; + } } )* result diff --git a/src/system/mod.rs b/src/system/mod.rs index a088593..2214667 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -73,7 +73,7 @@ pub unsafe trait System { /// Returns access type to the specified component type this system may perform. #[must_use] - fn component_access(&self, comp: &ComponentInfo) -> Option; + fn component_access(&self, archetype: &Archetype, comp: &ComponentInfo) -> Option; /// Returns access type to the specified resource type this system may perform. #[must_use] @@ -158,7 +158,7 @@ where true } - fn component_access(&self, _comp: &ComponentInfo) -> Option { + fn component_access(&self, _archetype: &Archetype, _comp: &ComponentInfo) -> Option { Some(Access::Write) } @@ -238,11 +238,11 @@ macro_rules! impl_system { } #[inline] - fn component_access(&self, comp: &ComponentInfo) -> Option { + fn component_access(&self, archetype: &Archetype, comp: &ComponentInfo) -> Option { let ($($a,)+) = &self.0; let mut result = None; $( - result = match (result, $a.component_access(comp)) { + result = match (result, $a.component_access(archetype, comp)) { (Some(Access::Write), _) | (_, Some(Access::Write)) => Some(Access::Write), (Some(Access::Read), _) | (_, Some(Access::Read)) => Some(Access::Read), (None, None) => None, diff --git a/src/test.rs b/src/test.rs index 1c6214a..13f7046 100644 --- a/src/test.rs +++ b/src/test.rs @@ -259,11 +259,11 @@ fn test_relation() { let a = world.spawn(()).id(); let b = world.spawn(()).id(); - world.add_relation(a, A, a).unwrap(); + world.insert_relation(a, A, a).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -276,11 +276,11 @@ fn test_relation() { assert_eq!(a_s[0].1.by_ref().count(), 0); - world.add_relation(a, A, b).unwrap(); + world.insert_relation(a, A, b).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -324,11 +324,11 @@ fn test_exclusive_relation() { let a = world.spawn(()).id(); let b = world.spawn(()).id(); - world.add_relation(a, A, a).unwrap(); + world.insert_relation(a, A, a).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -341,11 +341,11 @@ fn test_exclusive_relation() { assert_eq!(a_s[0].1.by_ref().count(), 0); - world.add_relation(a, A, b).unwrap(); + world.insert_relation(a, A, b).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -363,7 +363,7 @@ fn test_exclusive_relation() { assert_eq!( world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .count(), 0 @@ -385,11 +385,11 @@ fn test_symmetric_relation() { let a = world.spawn(()).id(); let b = world.spawn(()).id(); - world.add_relation(a, A, a).unwrap(); + world.insert_relation(a, A, a).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -402,11 +402,11 @@ fn test_symmetric_relation() { assert_eq!(a_s[0].1.by_ref().count(), 0); - world.add_relation(a, A, b).unwrap(); + world.insert_relation(a, A, b).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -458,11 +458,11 @@ fn test_symmetric_exclusive_relation() { let a = world.spawn(()).id(); let b = world.spawn(()).id(); - world.add_relation(a, A, a).unwrap(); + world.insert_relation(a, A, a).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -475,11 +475,11 @@ fn test_symmetric_exclusive_relation() { assert_eq!(a_s[0].1.by_ref().count(), 0); - world.add_relation(a, A, b).unwrap(); + world.insert_relation(a, A, b).unwrap(); let mut a_s = world .view_mut::() - .relates::() + .relates_ref::() .into_iter() .collect::>(); @@ -561,7 +561,7 @@ fn with_without() { } #[test] -fn add_relation() { +fn insert_relation() { let mut world = World::new(); let target = world.allocate().id(); @@ -571,7 +571,7 @@ fn add_relation() { struct Foo; world.insert(origin, Foo).unwrap(); - world.add_relation(origin, ChildOf, target).unwrap(); + world.insert_relation(origin, ChildOf, target).unwrap(); } #[cfg(feature = "flow")] diff --git a/src/view/extend.rs b/src/view/extend.rs index f9da8c4..6d70c1b 100644 --- a/src/view/extend.rs +++ b/src/view/extend.rs @@ -235,7 +235,17 @@ where /// The view will contain shared reference of the relation value /// and the target entity. #[inline(always)] - pub fn relates( + pub fn relates( + self, + ) -> ViewValue<'a, TupleQueryAdd>>, F, B, Extensible> { + self.extend(Relates::>) + } + + /// Queries for origin entities in relation of type `R`. + /// The view will contain shared reference of the relation value + /// and the target entity. + #[inline(always)] + pub fn relates_ref( self, ) -> ViewValue<'a, TupleQueryAdd>, F, B, Extensible> { self.extend(Relates::>) @@ -277,7 +287,17 @@ where /// The view will contain shared reference of the relation value /// and the target entity. #[inline(always)] - pub fn relates_exclusive( + pub fn relates_exclusive( + self, + ) -> ViewValue<'a, TupleQueryAdd>>, F, B, Extensible> { + self.extend(RelatesExclusive::>) + } + + /// Queries for origin entities in relation of type `R`. + /// The view will contain shared reference of the relation value + /// and the target entity. + #[inline(always)] + pub fn relates_exclusive_ref( self, ) -> ViewValue<'a, TupleQueryAdd>, F, B, Extensible> { self.extend(RelatesExclusive::>) @@ -298,8 +318,26 @@ where #[inline(always)] pub fn related( self, - ) -> ViewValue<'a, TupleQueryAdd>, F, B, Extensible> { - self.extend(Related) + ) -> ViewValue<'a, TupleQueryAdd>>, F, B, Extensible> { + self.extend(Related::>) + } + + /// Queries for target entities in relation of type `R`. + /// The view will contain origins of the relation. + #[inline(always)] + pub fn related_ref( + self, + ) -> ViewValue<'a, TupleQueryAdd>>, F, B, Extensible> { + self.extend(Related::>) + } + + /// Queries for target entities in relation of type `R`. + /// The view will contain origins of the relation. + #[inline(always)] + pub fn related_mut( + self, + ) -> ViewValue<'a, TupleQueryAdd>>, F, B, Extensible> { + self.extend(Related::>) } } diff --git a/src/world/relation.rs b/src/world/relation.rs index 4345233..599de4d 100644 --- a/src/world/relation.rs +++ b/src/world/relation.rs @@ -23,7 +23,7 @@ impl World { /// If relation is exclusive, then previous relation on origin is replaced, otherwise relation is added. /// If relation is exclusive and symmetric, then previous relation on target is replaced, otherwise relation is added. #[inline(always)] - pub fn add_relation( + pub fn insert_relation( &mut self, origin: impl Entity, relation: R, @@ -40,45 +40,49 @@ impl World { self.epoch.next_mut(); if R::SYMMETRIC { - set_relation_component( + let set_target = set_relation_component( self, origin.id(), relation, |relation| OriginComponent::new_relation(target.id(), relation), |component, relation, encoder| { - component.add_relation(origin.id(), target.id(), relation, encoder) + component.insert_relation(origin.id(), target.id(), relation, encoder) }, ); - if target.id() != origin.id() { + if target.id() != origin.id() && set_target { set_relation_component( self, target.id(), relation, |relation| OriginComponent::new_relation(origin.id(), relation), |component, relation, encoder| { - component.add_relation(target.id(), origin.id(), relation, encoder) + component.insert_relation(target.id(), origin.id(), relation, encoder) }, ); } } else { - set_relation_component( + let set_target = set_relation_component( self, origin.id(), relation, |relation| OriginComponent::new_relation(target.id(), relation), |comp, relation, encoder| { - comp.add_relation(origin.id(), target.id(), relation, encoder) + comp.insert_relation(origin.id(), target.id(), relation, encoder) }, ); - - set_relation_component( - self, - target.id(), - (), - |()| TargetComponent::::new(origin.id()), - |comp, (), _| comp.add(origin.id()), - ); + if set_target { + set_relation_component( + self, + target.id(), + (), + |()| TargetComponent::::new(origin.id(), relation), + |comp, (), _| { + comp.add(origin.id(), relation); + false + }, + ); + } } self.execute_local_actions(); @@ -101,7 +105,7 @@ impl World { where R: Relation, { - self._remove_relation(origin, target, |_, _, _, _| {}) + self._remove_relation(origin, target) } /// Drops relation between two entities in the [`World`]. @@ -119,7 +123,11 @@ impl World { where R: Relation, { - self._remove_relation(origin, target, R::on_drop)?; + if let Some(relation) = self._remove_relation(origin, target)? { + let encoder = LocalActionEncoder::new(self.action_buffer.get_mut(), &self.entities); + + R::on_drop(relation, origin.id(), target.id(), encoder); + } self.execute_local_actions(); Ok(()) } @@ -129,7 +137,6 @@ impl World { &mut self, origin: impl Entity, target: impl Entity, - on_drop: impl FnOnce(&mut R, EntityId, EntityId, LocalActionEncoder<'_>), ) -> Result, NoSuchEntity> where R: Relation, @@ -147,11 +154,9 @@ impl World { let mut encoder = LocalActionEncoder::new(&mut *self.action_buffer.get(), &self.entities); - if let Some(mut relation) = + if let Some(relation) = comp.remove_relation(origin.id(), target.id(), encoder.reborrow()) { - on_drop(&mut relation, origin.id(), target.id(), encoder.reborrow()); - if R::SYMMETRIC { if origin.id() != target.id() { let comp = self @@ -186,8 +191,9 @@ fn set_relation_component( id: EntityId, value: T, into_component: impl FnOnce(T) -> C, - set_component: impl FnOnce(&mut C, T, LocalActionEncoder), -) where + set_component: impl FnOnce(&mut C, T, LocalActionEncoder) -> bool, +) -> bool +where C: Component, { let src_loc = world.entities.get_location(id).unwrap(); @@ -200,9 +206,7 @@ fn set_relation_component( }; let encoders = LocalActionEncoder::new(world.action_buffer.get_mut(), &world.entities); - set_component(component, value, encoders); - world.execute_local_actions(); - return; + return set_component(component, value, encoders); } let component = into_component(value); @@ -236,4 +240,6 @@ fn set_relation_component( if let Some(src_id) = opt_src_id { world.entities.set_location(src_id, src_loc); } + + return true; } diff --git a/src/world/remove.rs b/src/world/remove.rs index 6da982e..a9ffbda 100644 --- a/src/world/remove.rs +++ b/src/world/remove.rs @@ -3,7 +3,7 @@ use core::any::{type_name, TypeId}; use crate::{ action::LocalActionEncoder, bundle::Bundle, - entity::{Entity, EntityRef, Location}, + entity::{Entity, EntityId, EntityRef, Location}, type_id, NoSuchEntity, }; @@ -79,6 +79,17 @@ impl World { self.drop_erased(entity, type_id::()) } + /// Drops component from the specified entity. + /// + /// Returns `Err(NoSuchEntity)` if entity is not alive. + #[inline(always)] + pub fn drop_batch(&mut self, entities: impl IntoIterator) + where + T: 'static, + { + self.drop_erased_batch(entities, type_id::()) + } + /// Drops component from the specified entity. /// /// Returns `Err(NoSuchEntity)` if entity is not alive. @@ -124,6 +135,54 @@ impl World { Ok(()) } + /// Drops component from the specified entity. + /// + /// Returns `Err(NoSuchEntity)` if entity is not alive. + #[inline(always)] + pub fn drop_erased_batch(&mut self, entities: impl IntoIterator, ty: TypeId) { + self.maintenance(); + + for entity in entities { + let Some(src_loc) = entity.lookup(&self.entities) else { + continue; + }; + debug_assert!(src_loc.arch < u32::MAX, "Allocated entities were spawned"); + + if !self.archetypes[src_loc.arch as usize].has_component(ty) { + // Safety: entity is not moved + // Reference is created with correct location of entity in this world. + continue; + } + + let dst_arch = self.edges.remove(&mut self.archetypes, src_loc.arch, ty); + + debug_assert_ne!(src_loc.arch, dst_arch); + + let (before, after) = self + .archetypes + .split_at_mut(src_loc.arch.max(dst_arch) as usize); + + let (src, dst) = match src_loc.arch < dst_arch { + true => (&mut before[src_loc.arch as usize], &mut after[0]), + false => (&mut after[0], &mut before[dst_arch as usize]), + }; + + let encoder = LocalActionEncoder::new(self.action_buffer.get_mut(), &self.entities); + let (dst_idx, opt_src_id) = + unsafe { src.drop_bundle(entity, dst, src_loc.idx, encoder) }; + + let dst_loc = Location::new(dst_arch, dst_idx); + + self.entities.set_location(entity, dst_loc); + + if let Some(src_id) = opt_src_id { + self.entities.set_location(src_id, src_loc); + } + } + + self.execute_local_actions(); + } + /// Drops entity's components that are found in the specified bundle. /// /// If entity is not alive, fails with `Err(NoSuchEntity)`. diff --git a/src/world/spawn.rs b/src/world/spawn.rs index 5321e40..6bbe028 100644 --- a/src/world/spawn.rs +++ b/src/world/spawn.rs @@ -556,6 +556,42 @@ impl World { Ok(()) } + /// Despawns batch of entities with specified ids. + /// + /// # Example + /// + /// ``` + /// # use edict::{world::World, ExampleComponent}; + /// let mut world = World::new(); + /// let entity1 = world.spawn((ExampleComponent,)).id(); + /// let entity2 = world.spawn((ExampleComponent,)).id(); + /// + /// world.despawn_batch([entity1, entity2]); + /// + /// assert!(world.despawn(entity1).is_err(), "Already despawned"); + /// assert!(world.despawn(entity2).is_err(), "Already despawned"); + /// ``` + #[inline(always)] + pub fn despawn_batch(&mut self, entities: impl IntoIterator) { + self.maintenance(); + + for entity in entities { + let Some(loc) = self.entities.despawn(entity) else { + continue; + }; + let encoder = LocalActionEncoder::new(self.action_buffer.get_mut(), &self.entities); + let opt_id = unsafe { + self.archetypes[loc.arch as usize].despawn_unchecked(entity, loc.idx, encoder) + }; + + if let Some(id) = opt_id { + self.entities.set_location(id, loc) + } + } + + self.execute_local_actions(); + } + /// Special-case despawn method for [`EntityRef::despawn`]. /// This method uses branch elimination for non-existent entity case /// and prevents data dependencies between removing entity from