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::{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 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 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), Tick::new(4), 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 let mut inner = ptr.map_unchanged(|x| &mut x.0);
259 assert!(!inner.is_changed());
260
261 *inner = 64;
263 assert!(inner.is_changed());
264 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 world.increment_change_tick();
275 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 world.increment_change_tick();
301 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 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}