From 5bd7dddb1cff0d7eb77d80f21f6af917daeb6cf3 Mon Sep 17 00:00:00 2001 From: Etienne Date: Thu, 12 Sep 2024 23:14:04 +0200 Subject: [PATCH] Allow multiple trackers for a single component type --- src/change_tracker.rs | 105 +++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/src/change_tracker.rs b/src/change_tracker.rs index c5439619..5e3a2322 100644 --- a/src/change_tracker.rs +++ b/src/change_tracker.rs @@ -1,6 +1,7 @@ use core::mem; use alloc::vec::Vec; +use core::marker::PhantomData; use crate::{Component, Entity, PreparedQuery, With, Without, World}; @@ -13,19 +14,49 @@ use crate::{Component, Entity, PreparedQuery, With, Without, World}; /// which are expensive to compare and/or clone, consider instead tracking changes manually, e.g. /// by setting a flag in the component's `DerefMut` implementation. /// -/// Always use exactly one `ChangeTracker` per [`World`] per component type of interest. Using -/// multiple trackers of the same `T` on the same world, or using the same tracker across multiple -/// worlds, will produce unpredictable results. -pub struct ChangeTracker { - added: PreparedQuery>>, - changed: PreparedQuery<(&'static T, &'static mut Previous)>, - removed: PreparedQuery>, &'static T>>, +/// `ChangeTracker` expect a `Flag` type parameter which is used to distinguish between multiple +/// trackers of the same component type. This is necessary to prevent multiple trackers from +/// interfering with each other. The `Flag` type should be a unique type like an empty struct. +/// +/// Using the same tracker across multiple worlds, will produce unpredictable results. +/// +/// # Example +/// ```rust +/// # use hecs::*; +/// let mut world = World::new(); +/// +/// // Create a change tracker for `i32` components with a unique id +/// struct Flag; +/// let mut tracker = ChangeTracker::::new(); +/// +/// // Spawn an entity with an `i32` component +/// { +/// world.spawn((42_i32,)); +/// let mut changes = tracker.track(&mut world); +/// let added = changes.added().map(|(_, &value)| value).collect::>(); +/// assert_eq!(added, [42]); +/// } +/// +/// // Modify the component +/// { +/// for (_, value) in world.query_mut::<&mut i32>() { +/// *value += 1; +/// } +/// let mut changes = tracker.track(&mut world); +/// let changes = changes.changed().map(|(_, old, &new)| (old, new)).collect::>(); +/// assert_eq!(changes, [(42, 43)]); +/// } +/// ``` +pub struct ChangeTracker { + added: PreparedQuery>>, + changed: PreparedQuery<(&'static T, &'static mut Previous)>, + removed: PreparedQuery>, &'static T>>, added_components: Vec<(Entity, T)>, removed_components: Vec, } -impl ChangeTracker { +impl ChangeTracker { /// Create a change tracker for `T` components pub fn new() -> Self { Self { @@ -39,7 +70,7 @@ impl ChangeTracker { } /// Determine the changes in `T` components in `world` since the previous call - pub fn track<'a>(&'a mut self, world: &'a mut World) -> Changes<'a, T> + pub fn track<'a>(&'a mut self, world: &'a mut World) -> Changes<'a, T, F> where T: Clone + PartialEq, { @@ -53,29 +84,31 @@ impl ChangeTracker { } } -impl Default for ChangeTracker { +impl Default for ChangeTracker { fn default() -> Self { Self::new() } } -struct Previous(T); +struct Previous(T, PhantomData); -/// Collection of iterators over changes in `T` components -pub struct Changes<'a, T> +/// Collection of iterators over changes in `T` components given a flag `F` +pub struct Changes<'a, T, F> where T: Component + Clone + PartialEq, + F: Send + Sync + 'static, { - tracker: &'a mut ChangeTracker, + tracker: &'a mut ChangeTracker, world: &'a mut World, added: bool, changed: bool, removed: bool, } -impl<'a, T> Changes<'a, T> +impl<'a, T, F> Changes<'a, T, F> where T: Component + Clone + PartialEq, + F: Send + Sync + 'static, { /// Iterate over entities which were given a new `T` component after the preceding /// [`track`](ChangeTracker::track) call, including newly spawned entities @@ -121,12 +154,12 @@ where self.tracker .removed_components .drain(..) - .map(|e| (e, self.world.remove_one::>(e).unwrap().0)), + .map(|e| (e, self.world.remove_one::>(e).unwrap().0)), ) } } -impl<'a, T: Component> Drop for Changes<'a, T> +impl<'a, T: Component, F: Send + Sync + 'static> Drop for Changes<'a, T, F> where T: Component + Clone + PartialEq, { @@ -135,7 +168,9 @@ where _ = self.added(); } for (entity, component) in self.tracker.added_components.drain(..) { - self.world.insert_one(entity, Previous(component)).unwrap(); + self.world + .insert_one(entity, Previous(component, PhantomData::)) + .unwrap(); } if !self.changed { _ = self.changed(); @@ -182,9 +217,22 @@ mod tests { let b = world.spawn((17, false)); let c = world.spawn((true,)); - let mut tracker = ChangeTracker::::new(); + struct Flag1; + struct Flag2; + let mut tracker1 = ChangeTracker::::new(); + let mut tracker2 = ChangeTracker::::new(); + { - let mut changes = tracker.track(&mut world); + let mut changes = tracker1.track(&mut world); + let added = changes.added().collect::>(); + assert_eq!(added.len(), 2); + assert!(added.contains(&(a, &42))); + assert!(added.contains(&(b, &17))); + assert_eq!(changes.changed().count(), 0); + assert_eq!(changes.removed().count(), 0); + } + { + let mut changes = tracker2.track(&mut world); let added = changes.added().collect::>(); assert_eq!(added.len(), 2); assert!(added.contains(&(a, &42))); @@ -197,13 +245,26 @@ mod tests { *world.get::<&mut i32>(b).unwrap() = 26; world.insert_one(c, 74).unwrap(); { - let mut changes = tracker.track(&mut world); + let mut changes = tracker1.track(&mut world); + assert_eq!(changes.removed().collect::>(), [(a, 42)]); + assert_eq!(changes.changed().collect::>(), [(b, 17, &26)]); + assert_eq!(changes.added().collect::>(), [(c, &74)]); + } + { + let mut changes = tracker1.track(&mut world); + assert_eq!(changes.removed().collect::>(), []); + assert_eq!(changes.changed().collect::>(), []); + assert_eq!(changes.added().collect::>(), []); + } + + { + let mut changes = tracker2.track(&mut world); assert_eq!(changes.removed().collect::>(), [(a, 42)]); assert_eq!(changes.changed().collect::>(), [(b, 17, &26)]); assert_eq!(changes.added().collect::>(), [(c, &74)]); } { - let mut changes = tracker.track(&mut world); + let mut changes = tracker2.track(&mut world); assert_eq!(changes.removed().collect::>(), []); assert_eq!(changes.changed().collect::>(), []); assert_eq!(changes.added().collect::>(), []);