Path: blob/main/crates/bevy_ecs/src/change_detection/tick.rs
7219 views
use bevy_ecs_macros::Event;1#[cfg(feature = "bevy_reflect")]2use bevy_reflect::Reflect;3use core::{cell::UnsafeCell, panic::Location};45use crate::change_detection::{MaybeLocation, MAX_CHANGE_AGE};67/// A value that tracks when a system ran relative to other systems.8/// This is used to power change detection.9///10/// *Note* that a system that hasn't been run yet has a `Tick` of 0.11#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]12#[cfg_attr(13feature = "bevy_reflect",14derive(Reflect),15reflect(Debug, Hash, PartialEq, Clone)16)]17pub struct Tick {18tick: u32,19}2021impl Tick {22/// The maximum relative age for a change tick.23/// The value of this is equal to [`MAX_CHANGE_AGE`].24///25/// Since change detection will not work for any ticks older than this,26/// ticks are periodically scanned to ensure their relative values are below this.27pub const MAX: Self = Self::new(MAX_CHANGE_AGE);2829/// Creates a new [`Tick`] wrapping the given value.30#[inline]31pub const fn new(tick: u32) -> Self {32Self { tick }33}3435/// Gets the value of this change tick.36#[inline]37pub const fn get(self) -> u32 {38self.tick39}4041/// Sets the value of this change tick.42#[inline]43pub fn set(&mut self, tick: u32) {44self.tick = tick;45}4647/// Returns `true` if this `Tick` occurred since the system's `last_run`.48///49/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.50#[inline]51pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {52// This works even with wraparound because the world tick (`this_run`) is always "newer" than53// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values54// so they never get older than `u32::MAX` (the difference would overflow).55//56// The clamp here ensures determinism (since scans could differ between app runs).57let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);58let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);5960ticks_since_system > ticks_since_insert61}6263/// Returns a change tick representing the relationship between `self` and `other`.64#[inline]65pub(crate) fn relative_to(self, other: Self) -> Self {66let tick = self.tick.wrapping_sub(other.tick);67Self { tick }68}6970/// Wraps this change tick's value if it exceeds [`Tick::MAX`].71///72/// Returns `true` if wrapping was performed. Otherwise, returns `false`.73#[inline]74pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {75let age = check.present_tick().relative_to(*self);76// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true77// so long as this check always runs before that can happen.78if age.get() > Self::MAX.get() {79*self = check.present_tick().relative_to(Self::MAX);80true81} else {82false83}84}85}8687/// An [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make88/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus89/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.90///91/// # Example92///93/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change94/// ticks automatically updated via [`World::check_change_ticks`](crate::world::World::check_change_ticks),95/// possibly causing `Tick`-related bugs on long-running apps.96///97/// To fix that, add an observer for this event that calls the schedule's98/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).99///100/// ```101/// use bevy_ecs::prelude::*;102/// use bevy_ecs::change_detection::CheckChangeTicks;103///104/// #[derive(Resource)]105/// struct CustomSchedule(Schedule);106///107/// # let mut world = World::new();108/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {109/// schedule.0.check_change_ticks(*check);110/// });111/// ```112#[derive(Debug, Clone, Copy, Event)]113pub struct CheckChangeTicks(pub(crate) Tick);114115impl CheckChangeTicks {116/// Get the present `Tick` that other ticks get compared to.117pub fn present_tick(self) -> Tick {118self.0119}120}121122/// Interior-mutable access to the [`Tick`]s of a single component or resource.123#[derive(Copy, Clone, Debug)]124pub struct ComponentTickCells<'a> {125/// The tick indicating when the value was added to the world.126pub added: &'a UnsafeCell<Tick>,127/// The tick indicating the last time the value was modified.128pub changed: &'a UnsafeCell<Tick>,129/// The calling location that last modified the value.130pub changed_by: MaybeLocation<&'a UnsafeCell<&'static Location<'static>>>,131}132133/// Records when a component or resource was added and when it was last mutably dereferenced (or added).134#[derive(Copy, Clone, Debug)]135#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]136pub struct ComponentTicks {137/// Tick recording the time this component or resource was added.138pub added: Tick,139140/// Tick recording the time this component or resource was most recently changed.141pub changed: Tick,142}143144impl ComponentTicks {145/// Returns `true` if the component or resource was added after the system last ran146/// (or the system is running for the first time).147#[inline]148pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {149self.added.is_newer_than(last_run, this_run)150}151152/// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran153/// (or the system is running for the first time).154#[inline]155pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {156self.changed.is_newer_than(last_run, this_run)157}158159/// Creates a new instance with the same change tick for `added` and `changed`.160pub fn new(change_tick: Tick) -> Self {161Self {162added: change_tick,163changed: change_tick,164}165}166167/// Manually sets the change tick.168///169/// This is normally done automatically via the [`DerefMut`](core::ops::DerefMut) implementation170/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.171/// However, components and resources that make use of interior mutability might require manual updates.172///173/// # Example174/// ```no_run175/// # use bevy_ecs::{world::World, change_detection::ComponentTicks};176/// let world: World = unimplemented!();177/// let component_ticks: ComponentTicks = unimplemented!();178///179/// component_ticks.set_changed(world.read_change_tick());180/// ```181#[inline]182pub fn set_changed(&mut self, change_tick: Tick) {183self.changed = change_tick;184}185}186187188