bevy_ecs/change_detection/
mod.rs1mod 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
13pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000;
22
23pub 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::{CreateTypeData, ReflectFromPtr};
33 use core::ops::{Deref, DerefMut};
34
35 use crate::{
36 change_detection::{
37 AtomicTick, ComponentTicks, ComponentTicksMut, MaybeLocation, Mut, NonSendMut, Ref,
38 ResMut, Tick, 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 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 assert!(change_detected_system.run((), &mut world).unwrap());
92
93 let change_tick = world.change_tick.get_mut();
95 *change_tick = change_tick.wrapping_add(MAX_CHANGE_AGE);
96
97 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 world.spawn(C);
112
113 world.increment_change_tick();
114
115 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 world.spawn(C);
127
128 *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 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 summary_tick = AtomicTick::default();
159 let ticks = ComponentTicksMut {
160 added: &mut component_ticks.added,
161 changed: &mut component_ticks.changed,
162 changed_by: caller.as_mut(),
163 last_run: Tick::new(3),
164 this_run: Tick::new(4),
165 summary_tick: Some(&summary_tick),
166 };
167 let mut res = R {};
168
169 let res_mut = ResMut {
170 value: &mut res,
171 ticks,
172 };
173
174 let into_mut: Mut<R> = res_mut.into();
175 assert_eq!(1, into_mut.ticks.added.get());
176 assert_eq!(2, into_mut.ticks.changed.get());
177 assert_eq!(3, into_mut.ticks.last_run.get());
178 assert_eq!(4, into_mut.ticks.this_run.get());
179 }
180
181 #[test]
182 fn mut_new() {
183 let mut component_ticks = ComponentTicks {
184 added: Tick::new(1),
185 changed: Tick::new(3),
186 };
187 let mut res = R {};
188 let mut caller = MaybeLocation::caller();
189 let summary_tick = AtomicTick::default();
190
191 let val = Mut::new(
192 &mut res,
193 &mut component_ticks.added,
194 &mut component_ticks.changed,
195 Some(&summary_tick),
196 Tick::new(2), Tick::new(4), caller.as_mut(),
199 );
200
201 assert!(!val.is_added());
202 assert!(val.is_changed());
203 }
204
205 #[test]
206 fn mut_from_non_send_mut() {
207 let mut component_ticks = ComponentTicks {
208 added: Tick::new(1),
209 changed: Tick::new(2),
210 };
211 let mut caller = MaybeLocation::caller();
212 let summary_tick = AtomicTick::default();
213 let ticks = ComponentTicksMut {
214 added: &mut component_ticks.added,
215 changed: &mut component_ticks.changed,
216 changed_by: caller.as_mut(),
217 last_run: Tick::new(3),
218 this_run: Tick::new(4),
219 summary_tick: Some(&summary_tick),
220 };
221 let mut res = R {};
222
223 let non_send_mut = NonSendMut {
224 value: &mut res,
225 ticks,
226 };
227
228 let into_mut: Mut<R> = non_send_mut.into();
229 assert_eq!(1, into_mut.ticks.added.get());
230 assert_eq!(2, into_mut.ticks.changed.get());
231 assert_eq!(3, into_mut.ticks.last_run.get());
232 assert_eq!(4, into_mut.ticks.this_run.get());
233 }
234
235 #[test]
236 fn map_mut() {
237 use super::*;
238 struct Outer(i64);
239
240 let last_run = Tick::new(2);
241 let this_run = Tick::new(3);
242 let mut component_ticks = ComponentTicks {
243 added: Tick::new(1),
244 changed: Tick::new(2),
245 };
246 let mut caller = MaybeLocation::caller();
247 let summary_tick = AtomicTick::default();
248 let ticks = ComponentTicksMut {
249 added: &mut component_ticks.added,
250 changed: &mut component_ticks.changed,
251 changed_by: caller.as_mut(),
252 last_run,
253 this_run,
254 summary_tick: Some(&summary_tick),
255 };
256
257 let mut outer = Outer(0);
258
259 let ptr = Mut {
260 value: &mut outer,
261 ticks,
262 };
263 assert!(!ptr.is_changed());
264
265 let mut inner = ptr.map_unchanged(|x| &mut x.0);
267 assert!(!inner.is_changed());
268
269 *inner = 64;
271 assert!(inner.is_changed());
272 assert!(component_ticks.is_changed(last_run, this_run));
274 }
275
276 #[test]
277 fn set_if_neq() {
278 let mut world = World::new();
279
280 world.insert_resource(R2(0));
281 world.increment_change_tick();
283 world.clear_trackers();
285
286 let mut r = world.resource_mut::<R2>();
287 assert!(!r.is_changed(), "Resource must begin unchanged.");
288
289 r.set_if_neq(R2(0));
290 assert!(
291 !r.is_changed(),
292 "Resource must not be changed after setting to the same value."
293 );
294
295 r.set_if_neq(R2(3));
296 assert!(
297 r.is_changed(),
298 "Resource must be changed after setting to a different value."
299 );
300 }
301
302 #[test]
303 fn as_deref_mut() {
304 let mut world = World::new();
305
306 world.insert_resource(R2(0));
307 world.increment_change_tick();
309 world.clear_trackers();
311
312 let mut r = world.resource_mut::<R2>();
313 assert!(!r.is_changed(), "Resource must begin unchanged.");
314
315 let mut r = r.as_deref_mut();
316 assert!(
317 !r.is_changed(),
318 "Dereferencing should not mark the item as changed yet"
319 );
320
321 r.set_if_neq(3);
322 assert!(
323 r.is_changed(),
324 "Resource must be changed after setting to a different value."
325 );
326 }
327
328 #[test]
329 fn mut_untyped_to_reflect() {
330 let last_run = Tick::new(2);
331 let this_run = Tick::new(3);
332 let mut component_ticks = ComponentTicks {
333 added: Tick::new(1),
334 changed: Tick::new(2),
335 };
336 let mut caller = MaybeLocation::caller();
337 let summary_tick = AtomicTick::default();
338 let ticks = ComponentTicksMut {
339 added: &mut component_ticks.added,
340 changed: &mut component_ticks.changed,
341 changed_by: caller.as_mut(),
342 last_run,
343 this_run,
344 summary_tick: Some(&summary_tick),
345 };
346
347 let mut value: i32 = 5;
348
349 let value = MutUntyped {
350 value: PtrMut::from(&mut value),
351 ticks,
352 };
353
354 let reflect_from_ptr = <ReflectFromPtr as CreateTypeData<i32>>::create_type_data(());
355
356 let mut new = value.map_unchanged(|ptr| {
357 unsafe { reflect_from_ptr.ptr_as_reflect_mut(ptr) }
359 });
360
361 assert!(!new.is_changed());
362
363 new.reflect_mut();
364
365 assert!(new.is_changed());
366 }
367
368 #[test]
369 fn mut_untyped_from_mut() {
370 let mut component_ticks = ComponentTicks {
371 added: Tick::new(1),
372 changed: Tick::new(2),
373 };
374 let mut caller = MaybeLocation::caller();
375 let summary_tick = AtomicTick::default();
376 let ticks = ComponentTicksMut {
377 added: &mut component_ticks.added,
378 changed: &mut component_ticks.changed,
379 changed_by: caller.as_mut(),
380 last_run: Tick::new(3),
381 this_run: Tick::new(4),
382 summary_tick: Some(&summary_tick),
383 };
384 let mut c = C {};
385
386 let mut_typed = Mut {
387 value: &mut c,
388 ticks,
389 };
390
391 let into_mut: MutUntyped = mut_typed.into();
392 assert_eq!(1, into_mut.ticks.added.get());
393 assert_eq!(2, into_mut.ticks.changed.get());
394 assert_eq!(3, into_mut.ticks.last_run.get());
395 assert_eq!(4, into_mut.ticks.this_run.get());
396 }
397}