Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/change_detection/mod.rs
7219 views
1
//! Types that detect when their internal data mutate.
2
3
mod maybe_location;
4
mod params;
5
mod tick;
6
mod traits;
7
8
pub use maybe_location::MaybeLocation;
9
pub use params::*;
10
pub use tick::*;
11
pub 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)
21
pub 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.
26
pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
27
28
#[cfg(test)]
29
mod 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
}
386
387