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