bevy_ecs/change_detection/tick.rs
1use bevy_ecs_macros::Event;
2#[cfg(feature = "bevy_reflect")]
3use bevy_reflect::Reflect;
4use core::{
5 cell::UnsafeCell,
6 fmt::{self, Debug, Formatter},
7 panic::Location,
8 sync::atomic::{AtomicU32, Ordering},
9};
10
11use crate::change_detection::{MaybeLocation, MAX_CHANGE_AGE};
12
13/// A value that tracks when a system ran relative to other systems.
14/// This is used to power change detection.
15///
16/// *Note* that a system that hasn't been run yet has a `Tick` of 0.
17#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
18#[cfg_attr(
19 feature = "bevy_reflect",
20 derive(Reflect),
21 reflect(Debug, Hash, PartialEq, Clone)
22)]
23pub struct Tick {
24 tick: u32,
25}
26
27impl Tick {
28 /// The maximum relative age for a change tick.
29 /// The value of this is equal to [`MAX_CHANGE_AGE`].
30 ///
31 /// Since change detection will not work for any ticks older than this,
32 /// ticks are periodically scanned to ensure their relative values are below this.
33 pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
34
35 /// Creates a new [`Tick`] wrapping the given value.
36 #[inline]
37 pub const fn new(tick: u32) -> Self {
38 Self { tick }
39 }
40
41 /// Gets the value of this change tick.
42 #[inline]
43 pub const fn get(self) -> u32 {
44 self.tick
45 }
46
47 /// Sets the value of this change tick.
48 #[inline]
49 pub fn set(&mut self, tick: u32) {
50 self.tick = tick;
51 }
52
53 /// Returns `true` if this `Tick` occurred since the system's `last_run`.
54 ///
55 /// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
56 #[inline]
57 pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
58 // This works even with wraparound because the world tick (`this_run`) is always "newer" than
59 // `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
60 // so they never get older than `u32::MAX` (the difference would overflow).
61 //
62 // The clamp here ensures determinism (since scans could differ between app runs).
63 let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
64 let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
65
66 ticks_since_system > ticks_since_insert
67 }
68
69 /// Returns a change tick representing the relationship between `self` and `other`.
70 #[inline]
71 pub(crate) fn relative_to(self, other: Self) -> Self {
72 let tick = self.tick.wrapping_sub(other.tick);
73 Self { tick }
74 }
75
76 /// Wraps this change tick's value if it exceeds [`Tick::MAX`].
77 ///
78 /// Returns `true` if wrapping was performed. Otherwise, returns `false`.
79 #[inline]
80 pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
81 let age = check.present_tick().relative_to(*self);
82 // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
83 // so long as this check always runs before that can happen.
84 if age.get() > Self::MAX.get() {
85 *self = check.present_tick().relative_to(Self::MAX);
86 true
87 } else {
88 false
89 }
90 }
91}
92
93/// A tick that can be updated from multiple threads.
94///
95/// This has exactly the same semantics as [`Tick`] but can be updated
96/// atomically. It's used for summary ticks.
97#[derive(Default)]
98pub struct AtomicTick {
99 /// The atomic tick value.
100 tick: AtomicU32,
101}
102
103impl AtomicTick {
104 /// Returns the current value of the tick.
105 pub fn get(&self) -> Tick {
106 Tick {
107 tick: self.tick.load(Ordering::Relaxed),
108 }
109 }
110
111 #[expect(
112 clippy::doc_markdown,
113 reason = "The word 'ARMv6' does not require backticks"
114 )]
115 /// Sets a new value for the tick.
116 ///
117 /// Note that this method takes `&self` and can therefore be called from
118 /// multiple threads. The tick is updated using relaxed ordering and is
119 /// therefore cheap to update on common architectures (x86-64, ARMv6 and
120 /// newer). However, be warned that, because it uses relaxed ordering, a
121 /// full mutex lock or similar barrier is required if you need to coordinate
122 /// the synchronization of this value with other memory locations.
123 pub fn set(&self, new_tick: Tick) {
124 // Do an unsynchronized read first.
125 //
126 // This is important on x86-64 (both Intel and AMD) to avoid a
127 // performance cliff. It has much smaller effects on AArch64, but it's
128 // still worth doing on e.g. Apple M2 in parallel mode, so I'm leaving
129 // it in.
130 //
131 // See benchmarks:
132 // https://github.com/bevyengine/bevy/pull/25157#issuecomment-5242287382
133 if self.tick.load(Ordering::Relaxed) != new_tick.get() {
134 self.tick.store(new_tick.get(), Ordering::Relaxed);
135 }
136 }
137}
138
139impl Debug for AtomicTick {
140 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141 f.debug_struct("AtomicTick")
142 .field("tick", &self.get().get())
143 .finish()
144 }
145}
146
147/// An [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
148/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
149/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
150///
151/// # Example
152///
153/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
154/// ticks automatically updated via [`World::check_change_ticks`](crate::world::World::check_change_ticks),
155/// possibly causing `Tick`-related bugs on long-running apps.
156///
157/// To fix that, add an observer for this event that calls the schedule's
158/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
159///
160/// ```
161/// use bevy_ecs::prelude::*;
162/// use bevy_ecs::change_detection::CheckChangeTicks;
163///
164/// #[derive(Resource)]
165/// struct CustomSchedule(Schedule);
166///
167/// # let mut world = World::new();
168/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
169/// schedule.0.check_change_ticks(*check);
170/// });
171/// ```
172#[derive(Debug, Clone, Copy, Event)]
173pub struct CheckChangeTicks(pub(crate) Tick);
174
175impl CheckChangeTicks {
176 /// Get the present `Tick` that other ticks get compared to.
177 pub fn present_tick(self) -> Tick {
178 self.0
179 }
180}
181
182/// Interior-mutable access to the [`Tick`]s of a single component or resource.
183#[derive(Copy, Clone, Debug)]
184pub struct ComponentTickCells<'a> {
185 /// The tick indicating when the value was added to the world.
186 pub added: &'a UnsafeCell<Tick>,
187 /// The tick indicating the last time the value was modified.
188 pub changed: &'a UnsafeCell<Tick>,
189 /// The calling location that last modified the value.
190 pub changed_by: MaybeLocation<&'a UnsafeCell<&'static Location<'static>>>,
191 /// The summary tick for the column, if the component is dense and has a
192 /// summary tick.
193 pub summary_tick: Option<&'a AtomicTick>,
194}
195
196/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
197#[derive(Copy, Clone, Debug)]
198#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
199pub struct ComponentTicks {
200 /// Tick recording the time this component or resource was added.
201 pub added: Tick,
202
203 /// Tick recording the time this component or resource was most recently changed.
204 pub changed: Tick,
205}
206
207impl ComponentTicks {
208 /// Returns `true` if the component or resource was added after the system last ran
209 /// (or the system is running for the first time).
210 #[inline]
211 pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
212 self.added.is_newer_than(last_run, this_run)
213 }
214
215 /// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran
216 /// (or the system is running for the first time).
217 #[inline]
218 pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
219 self.changed.is_newer_than(last_run, this_run)
220 }
221
222 /// Creates a new instance with the same change tick for `added` and `changed`.
223 pub fn new(change_tick: Tick) -> Self {
224 Self {
225 added: change_tick,
226 changed: change_tick,
227 }
228 }
229
230 /// Manually sets the change tick.
231 ///
232 /// This is normally done automatically via the [`DerefMut`](core::ops::DerefMut) implementation
233 /// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
234 /// However, components and resources that make use of interior mutability might require manual updates.
235 ///
236 /// # Example
237 /// ```no_run
238 /// # use bevy_ecs::{world::World, change_detection::ComponentTicks};
239 /// let world: World = unimplemented!();
240 /// let component_ticks: ComponentTicks = unimplemented!();
241 ///
242 /// component_ticks.set_changed(world.read_change_tick());
243 /// ```
244 #[inline]
245 pub fn set_changed(&mut self, change_tick: Tick) {
246 self.changed = change_tick;
247 }
248}