Skip to main content

ecsx/change_tracker/
mod.rs

1use core::mem;
2
3use alloc::vec::Vec;
4
5use crate::{Component, Entity, PreparedQuery, Substrate, With, Without};
6
7/// Helper to track changes in `T` components
8///
9/// For each entity with a `T` component, a private component is inserted which stores the value as
10/// of the most recent call to `track`. This provides robust, exact change detection at the cost of
11/// visiting each possibly-changed entity. It is a good fit for entities that will typically be
12/// visited regardless, and components having fast [`Clone`] and [`PartialEq`] impls. For components
13/// which are expensive to compare and/or clone, consider instead tracking changes manually, e.g.
14/// by setting a flag in the component's `DerefMut` implementation.
15///
16/// Always use exactly one `ChangeTracker` per [`Substrate`] per component type of interest. Using
17/// multiple trackers of the same `T` on the same world, or using the same tracker across multiple
18/// worlds, will produce unpredictable results.
19pub struct ChangeTracker<T: Component> {
20    added: PreparedQuery<Without<(Entity, &'static T), &'static Previous<T>>>,
21    changed: PreparedQuery<(Entity, &'static T, &'static mut Previous<T>)>,
22    removed: PreparedQuery<Without<With<Entity, &'static Previous<T>>, &'static T>>,
23
24    added_components: Vec<(Entity, T)>,
25    removed_components: Vec<Entity>,
26}
27
28impl<T: Component> ChangeTracker<T> {
29    /// Create a change tracker for `T` components
30    pub fn new() -> Self {
31        Self {
32            added: PreparedQuery::new(),
33            changed: PreparedQuery::new(),
34            removed: PreparedQuery::new(),
35
36            added_components: Vec::new(),
37            removed_components: Vec::new(),
38        }
39    }
40
41    /// Determine the changes in `T` components in `world` since the previous call
42    pub fn track<'a>(&'a mut self, world: &'a mut Substrate) -> Changes<'a, T>
43    where
44        T: Clone + PartialEq,
45    {
46        Changes {
47            tracker: self,
48            world,
49            added: false,
50            changed: false,
51            removed: false,
52        }
53    }
54}
55
56impl<T: Component> Default for ChangeTracker<T> {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62struct Previous<T>(T);
63
64/// Collection of iterators over changes in `T` components
65pub struct Changes<'a, T>
66where
67    T: Component + Clone + PartialEq,
68{
69    tracker: &'a mut ChangeTracker<T>,
70    world: &'a mut Substrate,
71    added: bool,
72    changed: bool,
73    removed: bool,
74}
75
76impl<'a, T> Changes<'a, T>
77where
78    T: Component + Clone + PartialEq,
79{
80    /// Iterate over entities which were given a new `T` component after the preceding
81    /// [`track`](ChangeTracker::track) call, including newly spawned entities
82    pub fn added(&mut self) -> impl ExactSizeIterator<Item = (Entity, &T)> + '_ {
83        self.tracker.added_components.clear();
84        self.added = true;
85        let tracker = &mut *self.tracker;
86        let added = &mut tracker.added;
87        let added_components = &mut tracker.added_components;
88        DrainOnDrop(
89            added
90                .query_mut(self.world)
91                .inspect(move |&(e, x)| added_components.push((e, x.clone()))),
92        )
93    }
94
95    /// Iterate over `(entity, old, new)` for entities whose `T` component has changed according to
96    /// [`PartialEq`] after the preceding [`track`](ChangeTracker::track) call
97    pub fn changed(&mut self) -> impl Iterator<Item = (Entity, T, &T)> + '_ {
98        self.changed = true;
99        DrainOnDrop(
100            self.tracker
101                .changed
102                .query_mut(self.world)
103                .filter_map(|(e, new, old)| {
104                    (*new != old.0).then(|| {
105                        let old = mem::replace(&mut old.0, new.clone());
106                        (e, old, new)
107                    })
108                }),
109        )
110    }
111
112    /// Iterate over entities which lost their `T` component after the preceding
113    /// [`track`](ChangeTracker::track) call, excluding any entities which were despawned
114    pub fn removed(&mut self) -> impl ExactSizeIterator<Item = (Entity, T)> + '_ {
115        let tracker = &mut *self.tracker;
116        let removed = &mut tracker.removed;
117        let removed_components = &mut tracker.removed_components;
118        let world = &mut *self.world;
119
120        removed_components.clear();
121        self.removed = true;
122        // TODO: We could make this much more efficient by introducing a mechanism for queries to
123        // take ownership of components directly.
124        removed_components.extend(removed.query_mut(world));
125        DrainOnDrop(
126            removed_components
127                .drain(..)
128                .map(move |e| (e, world.remove_one::<Previous<T>>(e).unwrap().0)),
129        )
130    }
131}
132
133impl<T: Component> Drop for Changes<'_, T>
134where
135    T: Component + Clone + PartialEq,
136{
137    fn drop(&mut self) {
138        if !self.added {
139            _ = self.added();
140        }
141        for (entity, component) in self.tracker.added_components.drain(..) {
142            self.world.insert_one(entity, Previous(component)).unwrap();
143        }
144        if !self.changed {
145            _ = self.changed();
146        }
147        if !self.removed {
148            _ = self.removed();
149        }
150    }
151}
152
153/// Helper to ensure an iterator visits every element so that we can rely on the iterator's side
154/// effects
155struct DrainOnDrop<T: Iterator>(T);
156
157impl<T: Iterator> Iterator for DrainOnDrop<T> {
158    type Item = T::Item;
159
160    fn next(&mut self) -> Option<Self::Item> {
161        self.0.next()
162    }
163}
164
165impl<T: ExactSizeIterator> ExactSizeIterator for DrainOnDrop<T> {
166    fn len(&self) -> usize {
167        self.0.len()
168    }
169}
170
171impl<T: Iterator> Drop for DrainOnDrop<T> {
172    fn drop(&mut self) {
173        for _ in &mut self.0 {}
174    }
175}
176
177#[cfg(test)]
178mod tests;