Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/change_detection/tick.rs
7219 views
1
use bevy_ecs_macros::Event;
2
#[cfg(feature = "bevy_reflect")]
3
use bevy_reflect::Reflect;
4
use core::{cell::UnsafeCell, panic::Location};
5
6
use crate::change_detection::{MaybeLocation, MAX_CHANGE_AGE};
7
8
/// A value that tracks when a system ran relative to other systems.
9
/// This is used to power change detection.
10
///
11
/// *Note* that a system that hasn't been run yet has a `Tick` of 0.
12
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
13
#[cfg_attr(
14
feature = "bevy_reflect",
15
derive(Reflect),
16
reflect(Debug, Hash, PartialEq, Clone)
17
)]
18
pub struct Tick {
19
tick: u32,
20
}
21
22
impl Tick {
23
/// The maximum relative age for a change tick.
24
/// The value of this is equal to [`MAX_CHANGE_AGE`].
25
///
26
/// Since change detection will not work for any ticks older than this,
27
/// ticks are periodically scanned to ensure their relative values are below this.
28
pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
29
30
/// Creates a new [`Tick`] wrapping the given value.
31
#[inline]
32
pub const fn new(tick: u32) -> Self {
33
Self { tick }
34
}
35
36
/// Gets the value of this change tick.
37
#[inline]
38
pub const fn get(self) -> u32 {
39
self.tick
40
}
41
42
/// Sets the value of this change tick.
43
#[inline]
44
pub fn set(&mut self, tick: u32) {
45
self.tick = tick;
46
}
47
48
/// Returns `true` if this `Tick` occurred since the system's `last_run`.
49
///
50
/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
51
#[inline]
52
pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
53
// This works even with wraparound because the world tick (`this_run`) is always "newer" than
54
// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
55
// so they never get older than `u32::MAX` (the difference would overflow).
56
//
57
// The clamp here ensures determinism (since scans could differ between app runs).
58
let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
59
let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
60
61
ticks_since_system > ticks_since_insert
62
}
63
64
/// Returns a change tick representing the relationship between `self` and `other`.
65
#[inline]
66
pub(crate) fn relative_to(self, other: Self) -> Self {
67
let tick = self.tick.wrapping_sub(other.tick);
68
Self { tick }
69
}
70
71
/// Wraps this change tick's value if it exceeds [`Tick::MAX`].
72
///
73
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
74
#[inline]
75
pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
76
let age = check.present_tick().relative_to(*self);
77
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
78
// so long as this check always runs before that can happen.
79
if age.get() > Self::MAX.get() {
80
*self = check.present_tick().relative_to(Self::MAX);
81
true
82
} else {
83
false
84
}
85
}
86
}
87
88
/// An [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
89
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
90
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
91
///
92
/// # Example
93
///
94
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
95
/// ticks automatically updated via [`World::check_change_ticks`](crate::world::World::check_change_ticks),
96
/// possibly causing `Tick`-related bugs on long-running apps.
97
///
98
/// To fix that, add an observer for this event that calls the schedule's
99
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
100
///
101
/// ```
102
/// use bevy_ecs::prelude::*;
103
/// use bevy_ecs::change_detection::CheckChangeTicks;
104
///
105
/// #[derive(Resource)]
106
/// struct CustomSchedule(Schedule);
107
///
108
/// # let mut world = World::new();
109
/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
110
/// schedule.0.check_change_ticks(*check);
111
/// });
112
/// ```
113
#[derive(Debug, Clone, Copy, Event)]
114
pub struct CheckChangeTicks(pub(crate) Tick);
115
116
impl CheckChangeTicks {
117
/// Get the present `Tick` that other ticks get compared to.
118
pub fn present_tick(self) -> Tick {
119
self.0
120
}
121
}
122
123
/// Interior-mutable access to the [`Tick`]s of a single component or resource.
124
#[derive(Copy, Clone, Debug)]
125
pub struct ComponentTickCells<'a> {
126
/// The tick indicating when the value was added to the world.
127
pub added: &'a UnsafeCell<Tick>,
128
/// The tick indicating the last time the value was modified.
129
pub changed: &'a UnsafeCell<Tick>,
130
/// The calling location that last modified the value.
131
pub changed_by: MaybeLocation<&'a UnsafeCell<&'static Location<'static>>>,
132
}
133
134
/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
135
#[derive(Copy, Clone, Debug)]
136
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
137
pub struct ComponentTicks {
138
/// Tick recording the time this component or resource was added.
139
pub added: Tick,
140
141
/// Tick recording the time this component or resource was most recently changed.
142
pub changed: Tick,
143
}
144
145
impl ComponentTicks {
146
/// Returns `true` if the component or resource was added after the system last ran
147
/// (or the system is running for the first time).
148
#[inline]
149
pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
150
self.added.is_newer_than(last_run, this_run)
151
}
152
153
/// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran
154
/// (or the system is running for the first time).
155
#[inline]
156
pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
157
self.changed.is_newer_than(last_run, this_run)
158
}
159
160
/// Creates a new instance with the same change tick for `added` and `changed`.
161
pub fn new(change_tick: Tick) -> Self {
162
Self {
163
added: change_tick,
164
changed: change_tick,
165
}
166
}
167
168
/// Manually sets the change tick.
169
///
170
/// This is normally done automatically via the [`DerefMut`](core::ops::DerefMut) implementation
171
/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
172
/// However, components and resources that make use of interior mutability might require manual updates.
173
///
174
/// # Example
175
/// ```no_run
176
/// # use bevy_ecs::{world::World, change_detection::ComponentTicks};
177
/// let world: World = unimplemented!();
178
/// let component_ticks: ComponentTicks = unimplemented!();
179
///
180
/// component_ticks.set_changed(world.read_change_tick());
181
/// ```
182
#[inline]
183
pub fn set_changed(&mut self, change_tick: Tick) {
184
self.changed = change_tick;
185
}
186
}
187
188