bevy_ecs/change_detection/
mod.rs

1//! Types that detect when their internal data mutate.
2
3mod maybe_location;
4mod params;
5mod tick;
6mod traits;
7
8pub use maybe_location::MaybeLocation;
9pub use params::*;
10pub use tick::*;
11pub use traits::{DetectChanges, DetectChangesMut};
12
13/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
14///
15/// Change ticks can only be scanned when systems aren't running. Thus, if the threshold is `N`,
16/// the maximum is `2 * N - 1` (i.e. the world ticks `N - 1` times, then `N` times).
17///
18/// If no change is older than `u32::MAX - (2 * N - 1)` following a scan, none of their ages can
19/// overflow and cause false positives.
20// (518,400,000 = 1000 ticks per frame * 144 frames per second * 3600 seconds per hour)
21pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000;
22
23/// The maximum change tick difference that won't overflow before the next `check_tick` scan.
24///
25/// Changes stop being detected once they become this old.
26pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
27
28#[cfg(test)]
29mod tests {
30    use bevy_ecs_macros::Resource;
31    use bevy_ptr::PtrMut;
32    use bevy_reflect::{FromType, ReflectFromPtr};
33    use core::ops::{Deref, DerefMut};
34
35    use crate::{
36        change_detection::{
37            ComponentTicks, ComponentTicksMut, MaybeLocation, Mut, NonSendMut, Ref, ResMut, Tick,
38            CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE,
39        },
40        component::Component,
41        system::{IntoSystem, Single, System},
42        world::World,
43    };
44
45    use super::{DetectChanges, DetectChangesMut, MutUntyped};
46
47    #[derive(Component, PartialEq)]
48    struct C;
49
50    #[derive(Resource)]
51    struct R;
52
53    #[derive(Resource, PartialEq)]
54    struct R2(u8);
55
56    impl Deref for R2 {
57        type Target = u8;
58        fn deref(&self) -> &u8 {
59            &self.0
60        }
61    }
62
63    impl DerefMut for R2 {
64        fn deref_mut(&mut self) -> &mut u8 {
65            &mut self.0
66        }
67    }
68
69    #[test]
70    fn change_expiration() {
71        fn change_detected(query: Option<Single<Ref<C>>>) -> bool {
72            query.unwrap().is_changed()
73        }
74
75        fn change_expired(query: Option<Single<Ref<C>>>) -> bool {
76            query.unwrap().is_changed()
77        }
78
79        let mut world = World::new();
80
81        // component added: 1, changed: 1
82        world.spawn(C);
83
84        let mut change_detected_system = IntoSystem::into_system(change_detected);
85        let mut change_expired_system = IntoSystem::into_system(change_expired);
86        change_detected_system.initialize(&mut world);
87        change_expired_system.initialize(&mut world);
88
89        // world: 1, system last ran: 0, component changed: 1
90        // The spawn will be detected since it happened after the system "last ran".
91        assert!(change_detected_system.run((), &mut world).unwrap());
92
93        // world: 1 + MAX_CHANGE_AGE
94        let change_tick = world.change_tick.get_mut();
95        *change_tick = change_tick.wrapping_add(MAX_CHANGE_AGE);
96
97        // Both the system and component appeared `MAX_CHANGE_AGE` ticks ago.
98        // Since we clamp things to `MAX_CHANGE_AGE` for determinism,
99        // `ComponentTicks::is_changed` will now see `MAX_CHANGE_AGE > MAX_CHANGE_AGE`
100        // and return `false`.
101        assert!(!change_expired_system.run((), &mut world).unwrap());
102    }
103
104    #[test]
105    fn change_tick_wraparound() {
106        let mut world = World::new();
107        world.last_change_tick = Tick::new(u32::MAX);
108        *world.change_tick.get_mut() = 0;
109
110        // component added: 0, changed: 0
111        world.spawn(C);
112
113        world.increment_change_tick();
114
115        // Since the world is always ahead, as long as changes can't get older than `u32::MAX` (which we ensure),
116        // the wrapping difference will always be positive, so wraparound doesn't matter.
117        let mut query = world.query::<Ref<C>>();
118        assert!(query.single(&world).unwrap().is_changed());
119    }
120
121    #[test]
122    fn change_tick_scan() {
123        let mut world = World::new();
124
125        // component added: 1, changed: 1
126        world.spawn(C);
127
128        // a bunch of stuff happens, the component is now older than `MAX_CHANGE_AGE`
129        *world.change_tick.get_mut() += MAX_CHANGE_AGE + CHECK_TICK_THRESHOLD;
130        let change_tick = world.change_tick();
131
132        let mut query = world.query::<Ref<C>>();
133        for tracker in query.iter(&world) {
134            let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get();
135            let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get();
136            assert!(ticks_since_insert > MAX_CHANGE_AGE);
137            assert!(ticks_since_change > MAX_CHANGE_AGE);
138        }
139
140        // scan change ticks and clamp those at risk of overflow
141        world.check_change_ticks();
142
143        for tracker in query.iter(&world) {
144            let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get();
145            let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get();
146            assert_eq!(ticks_since_insert, MAX_CHANGE_AGE);
147            assert_eq!(ticks_since_change, MAX_CHANGE_AGE);
148        }
149    }
150
151    #[test]
152    fn mut_from_res_mut() {
153        let mut component_ticks = ComponentTicks {
154            added: Tick::new(1),
155            changed: Tick::new(2),
156        };
157        let mut caller = MaybeLocation::caller();
158        let ticks = ComponentTicksMut {
159            added: &mut component_ticks.added,
160            changed: &mut component_ticks.changed,
161            changed_by: caller.as_mut(),
162            last_run: Tick::new(3),
163            this_run: Tick::new(4),
164        };
165        let mut res = R {};
166
167        let res_mut = ResMut {
168            value: &mut res,
169            ticks,
170        };
171
172        let into_mut: Mut<R> = res_mut.into();
173        assert_eq!(1, into_mut.ticks.added.get());
174        assert_eq!(2, into_mut.ticks.changed.get());
175        assert_eq!(3, into_mut.ticks.last_run.get());
176        assert_eq!(4, into_mut.ticks.this_run.get());
177    }
178
179    #[test]
180    fn mut_new() {
181        let mut component_ticks = ComponentTicks {
182            added: Tick::new(1),
183            changed: Tick::new(3),
184        };
185        let mut res = R {};
186        let mut caller = MaybeLocation::caller();
187
188        let val = Mut::new(
189            &mut res,
190            &mut component_ticks.added,
191            &mut component_ticks.changed,
192            Tick::new(2), // last_run
193            Tick::new(4), // this_run
194            caller.as_mut(),
195        );
196
197        assert!(!val.is_added());
198        assert!(val.is_changed());
199    }
200
201    #[test]
202    fn mut_from_non_send_mut() {
203        let mut component_ticks = ComponentTicks {
204            added: Tick::new(1),
205            changed: Tick::new(2),
206        };
207        let mut caller = MaybeLocation::caller();
208        let ticks = ComponentTicksMut {
209            added: &mut component_ticks.added,
210            changed: &mut component_ticks.changed,
211            changed_by: caller.as_mut(),
212            last_run: Tick::new(3),
213            this_run: Tick::new(4),
214        };
215        let mut res = R {};
216
217        let non_send_mut = NonSendMut {
218            value: &mut res,
219            ticks,
220        };
221
222        let into_mut: Mut<R> = non_send_mut.into();
223        assert_eq!(1, into_mut.ticks.added.get());
224        assert_eq!(2, into_mut.ticks.changed.get());
225        assert_eq!(3, into_mut.ticks.last_run.get());
226        assert_eq!(4, into_mut.ticks.this_run.get());
227    }
228
229    #[test]
230    fn map_mut() {
231        use super::*;
232        struct Outer(i64);
233
234        let last_run = Tick::new(2);
235        let this_run = Tick::new(3);
236        let mut component_ticks = ComponentTicks {
237            added: Tick::new(1),
238            changed: Tick::new(2),
239        };
240        let mut caller = MaybeLocation::caller();
241        let ticks = ComponentTicksMut {
242            added: &mut component_ticks.added,
243            changed: &mut component_ticks.changed,
244            changed_by: caller.as_mut(),
245            last_run,
246            this_run,
247        };
248
249        let mut outer = Outer(0);
250
251        let ptr = Mut {
252            value: &mut outer,
253            ticks,
254        };
255        assert!(!ptr.is_changed());
256
257        // Perform a mapping operation.
258        let mut inner = ptr.map_unchanged(|x| &mut x.0);
259        assert!(!inner.is_changed());
260
261        // Mutate the inner value.
262        *inner = 64;
263        assert!(inner.is_changed());
264        // Modifying one field of a component should flag a change for the entire component.
265        assert!(component_ticks.is_changed(last_run, this_run));
266    }
267
268    #[test]
269    fn set_if_neq() {
270        let mut world = World::new();
271
272        world.insert_resource(R2(0));
273        // Resources are Changed when first added
274        world.increment_change_tick();
275        // This is required to update world::last_change_tick
276        world.clear_trackers();
277
278        let mut r = world.resource_mut::<R2>();
279        assert!(!r.is_changed(), "Resource must begin unchanged.");
280
281        r.set_if_neq(R2(0));
282        assert!(
283            !r.is_changed(),
284            "Resource must not be changed after setting to the same value."
285        );
286
287        r.set_if_neq(R2(3));
288        assert!(
289            r.is_changed(),
290            "Resource must be changed after setting to a different value."
291        );
292    }
293
294    #[test]
295    fn as_deref_mut() {
296        let mut world = World::new();
297
298        world.insert_resource(R2(0));
299        // Resources are Changed when first added
300        world.increment_change_tick();
301        // This is required to update world::last_change_tick
302        world.clear_trackers();
303
304        let mut r = world.resource_mut::<R2>();
305        assert!(!r.is_changed(), "Resource must begin unchanged.");
306
307        let mut r = r.as_deref_mut();
308        assert!(
309            !r.is_changed(),
310            "Dereferencing should not mark the item as changed yet"
311        );
312
313        r.set_if_neq(3);
314        assert!(
315            r.is_changed(),
316            "Resource must be changed after setting to a different value."
317        );
318    }
319
320    #[test]
321    fn mut_untyped_to_reflect() {
322        let last_run = Tick::new(2);
323        let this_run = Tick::new(3);
324        let mut component_ticks = ComponentTicks {
325            added: Tick::new(1),
326            changed: Tick::new(2),
327        };
328        let mut caller = MaybeLocation::caller();
329        let ticks = ComponentTicksMut {
330            added: &mut component_ticks.added,
331            changed: &mut component_ticks.changed,
332            changed_by: caller.as_mut(),
333            last_run,
334            this_run,
335        };
336
337        let mut value: i32 = 5;
338
339        let value = MutUntyped {
340            value: PtrMut::from(&mut value),
341            ticks,
342        };
343
344        let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
345
346        let mut new = value.map_unchanged(|ptr| {
347            // SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`.
348            unsafe { reflect_from_ptr.as_reflect_mut(ptr) }
349        });
350
351        assert!(!new.is_changed());
352
353        new.reflect_mut();
354
355        assert!(new.is_changed());
356    }
357
358    #[test]
359    fn mut_untyped_from_mut() {
360        let mut component_ticks = ComponentTicks {
361            added: Tick::new(1),
362            changed: Tick::new(2),
363        };
364        let mut caller = MaybeLocation::caller();
365        let ticks = ComponentTicksMut {
366            added: &mut component_ticks.added,
367            changed: &mut component_ticks.changed,
368            changed_by: caller.as_mut(),
369            last_run: Tick::new(3),
370            this_run: Tick::new(4),
371        };
372        let mut c = C {};
373
374        let mut_typed = Mut {
375            value: &mut c,
376            ticks,
377        };
378
379        let into_mut: MutUntyped = mut_typed.into();
380        assert_eq!(1, into_mut.ticks.added.get());
381        assert_eq!(2, into_mut.ticks.changed.get());
382        assert_eq!(3, into_mut.ticks.last_run.get());
383        assert_eq!(4, into_mut.ticks.this_run.get());
384    }
385}