Skip to main content

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}