Extract tick related items from component
This commit is contained in:
parent
65e12a0f6d
commit
6092cb2158
@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
mod clone;
|
mod clone;
|
||||||
mod queued_registration;
|
mod queued_registration;
|
||||||
|
mod tick;
|
||||||
|
|
||||||
pub use clone::*;
|
pub use clone::*;
|
||||||
pub use queued_registration::*;
|
pub use queued_registration::*;
|
||||||
|
pub use tick::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::ArchetypeFlags,
|
archetype::ArchetypeFlags,
|
||||||
bundle::BundleInfo,
|
bundle::BundleInfo,
|
||||||
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
change_detection::MaybeLocation,
|
||||||
entity::{Entity, EntityMapper},
|
entity::{Entity, EntityMapper},
|
||||||
lifecycle::{ComponentHook, ComponentHooks},
|
lifecycle::{ComponentHook, ComponentHooks},
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
@ -20,20 +22,18 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use alloc::{borrow::Cow, format, vec::Vec};
|
use alloc::{borrow::Cow, format, vec::Vec};
|
||||||
pub use bevy_ecs_macros::Component;
|
pub use bevy_ecs_macros::Component;
|
||||||
use bevy_ecs_macros::Event;
|
|
||||||
use bevy_platform::sync::Arc;
|
use bevy_platform::sync::Arc;
|
||||||
use bevy_platform::{
|
use bevy_platform::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
sync::PoisonError,
|
sync::PoisonError,
|
||||||
};
|
};
|
||||||
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
|
use bevy_ptr::OwningPtr;
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_utils::{prelude::DebugName, TypeIdMap};
|
use bevy_utils::{prelude::DebugName, TypeIdMap};
|
||||||
use core::{
|
use core::{
|
||||||
alloc::Layout,
|
alloc::Layout,
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::UnsafeCell,
|
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem::needs_drop,
|
mem::needs_drop,
|
||||||
@ -2020,198 +2020,6 @@ impl Components {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value that tracks when a system ran relative to other systems.
|
|
||||||
/// This is used to power change detection.
|
|
||||||
///
|
|
||||||
/// *Note* that a system that hasn't been run yet has a `Tick` of 0.
|
|
||||||
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "bevy_reflect",
|
|
||||||
derive(Reflect),
|
|
||||||
reflect(Debug, Hash, PartialEq, Clone)
|
|
||||||
)]
|
|
||||||
pub struct Tick {
|
|
||||||
tick: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tick {
|
|
||||||
/// The maximum relative age for a change tick.
|
|
||||||
/// The value of this is equal to [`MAX_CHANGE_AGE`].
|
|
||||||
///
|
|
||||||
/// Since change detection will not work for any ticks older than this,
|
|
||||||
/// ticks are periodically scanned to ensure their relative values are below this.
|
|
||||||
pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
|
|
||||||
|
|
||||||
/// Creates a new [`Tick`] wrapping the given value.
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(tick: u32) -> Self {
|
|
||||||
Self { tick }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the value of this change tick.
|
|
||||||
#[inline]
|
|
||||||
pub const fn get(self) -> u32 {
|
|
||||||
self.tick
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the value of this change tick.
|
|
||||||
#[inline]
|
|
||||||
pub fn set(&mut self, tick: u32) {
|
|
||||||
self.tick = tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this `Tick` occurred since the system's `last_run`.
|
|
||||||
///
|
|
||||||
/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
|
|
||||||
// This works even with wraparound because the world tick (`this_run`) is always "newer" than
|
|
||||||
// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
|
|
||||||
// so they never get older than `u32::MAX` (the difference would overflow).
|
|
||||||
//
|
|
||||||
// The clamp here ensures determinism (since scans could differ between app runs).
|
|
||||||
let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
|
|
||||||
let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
|
|
||||||
|
|
||||||
ticks_since_system > ticks_since_insert
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a change tick representing the relationship between `self` and `other`.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn relative_to(self, other: Self) -> Self {
|
|
||||||
let tick = self.tick.wrapping_sub(other.tick);
|
|
||||||
Self { tick }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wraps this change tick's value if it exceeds [`Tick::MAX`].
|
|
||||||
///
|
|
||||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
|
||||||
#[inline]
|
|
||||||
pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
|
|
||||||
let age = check.present_tick().relative_to(*self);
|
|
||||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
|
||||||
// so long as this check always runs before that can happen.
|
|
||||||
if age.get() > Self::MAX.get() {
|
|
||||||
*self = check.present_tick().relative_to(Self::MAX);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
|
|
||||||
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
|
|
||||||
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
|
|
||||||
/// ticks automatically updated via [`World::check_change_ticks`], possibly causing `Tick`-related bugs on
|
|
||||||
/// long-running apps.
|
|
||||||
///
|
|
||||||
/// To fix that, add an observer for this event that calls the schedule's
|
|
||||||
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use bevy_ecs::prelude::*;
|
|
||||||
/// use bevy_ecs::component::CheckChangeTicks;
|
|
||||||
///
|
|
||||||
/// #[derive(Resource)]
|
|
||||||
/// struct CustomSchedule(Schedule);
|
|
||||||
///
|
|
||||||
/// # let mut world = World::new();
|
|
||||||
/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
|
|
||||||
/// schedule.0.check_change_ticks(*check);
|
|
||||||
/// });
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Copy, Event)]
|
|
||||||
pub struct CheckChangeTicks(pub(crate) Tick);
|
|
||||||
|
|
||||||
impl CheckChangeTicks {
|
|
||||||
/// Get the present `Tick` that other ticks get compared to.
|
|
||||||
pub fn present_tick(self) -> Tick {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct TickCells<'a> {
|
|
||||||
/// The tick indicating when the value was added to the world.
|
|
||||||
pub added: &'a UnsafeCell<Tick>,
|
|
||||||
/// The tick indicating the last time the value was modified.
|
|
||||||
pub changed: &'a UnsafeCell<Tick>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TickCells<'a> {
|
|
||||||
/// # Safety
|
|
||||||
/// All cells contained within must uphold the safety invariants of [`UnsafeCellDeref::read`].
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn read(&self) -> ComponentTicks {
|
|
||||||
ComponentTicks {
|
|
||||||
// SAFETY: The callers uphold the invariants for `read`.
|
|
||||||
added: unsafe { self.added.read() },
|
|
||||||
// SAFETY: The callers uphold the invariants for `read`.
|
|
||||||
changed: unsafe { self.changed.read() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
|
|
||||||
pub struct ComponentTicks {
|
|
||||||
/// Tick recording the time this component or resource was added.
|
|
||||||
pub added: Tick,
|
|
||||||
|
|
||||||
/// Tick recording the time this component or resource was most recently changed.
|
|
||||||
pub changed: Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComponentTicks {
|
|
||||||
/// Returns `true` if the component or resource was added after the system last ran
|
|
||||||
/// (or the system is running for the first time).
|
|
||||||
#[inline]
|
|
||||||
pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
|
|
||||||
self.added.is_newer_than(last_run, this_run)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran
|
|
||||||
/// (or the system is running for the first time).
|
|
||||||
#[inline]
|
|
||||||
pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
|
|
||||||
self.changed.is_newer_than(last_run, this_run)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance with the same change tick for `added` and `changed`.
|
|
||||||
pub fn new(change_tick: Tick) -> Self {
|
|
||||||
Self {
|
|
||||||
added: change_tick,
|
|
||||||
changed: change_tick,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manually sets the change tick.
|
|
||||||
///
|
|
||||||
/// This is normally done automatically via the [`DerefMut`] implementation
|
|
||||||
/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
|
|
||||||
/// However, components and resources that make use of interior mutability might require manual updates.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```no_run
|
|
||||||
/// # use bevy_ecs::{world::World, component::ComponentTicks};
|
|
||||||
/// let world: World = unimplemented!();
|
|
||||||
/// let component_ticks: ComponentTicks = unimplemented!();
|
|
||||||
///
|
|
||||||
/// component_ticks.set_changed(world.read_change_tick());
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn set_changed(&mut self, change_tick: Tick) {
|
|
||||||
self.changed = change_tick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [`SystemParam`] that provides access to the [`ComponentId`] for a specific component type.
|
/// A [`SystemParam`] that provides access to the [`ComponentId`] for a specific component type.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
199
crates/bevy_ecs/src/component/tick.rs
Normal file
199
crates/bevy_ecs/src/component/tick.rs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
use bevy_ecs_macros::Event;
|
||||||
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
|
#[cfg(feature = "bevy_reflect")]
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
use core::cell::UnsafeCell;
|
||||||
|
|
||||||
|
use crate::change_detection::MAX_CHANGE_AGE;
|
||||||
|
|
||||||
|
/// A value that tracks when a system ran relative to other systems.
|
||||||
|
/// This is used to power change detection.
|
||||||
|
///
|
||||||
|
/// *Note* that a system that hasn't been run yet has a `Tick` of 0.
|
||||||
|
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "bevy_reflect",
|
||||||
|
derive(Reflect),
|
||||||
|
reflect(Debug, Hash, PartialEq, Clone)
|
||||||
|
)]
|
||||||
|
pub struct Tick {
|
||||||
|
tick: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tick {
|
||||||
|
/// The maximum relative age for a change tick.
|
||||||
|
/// The value of this is equal to [`MAX_CHANGE_AGE`].
|
||||||
|
///
|
||||||
|
/// Since change detection will not work for any ticks older than this,
|
||||||
|
/// ticks are periodically scanned to ensure their relative values are below this.
|
||||||
|
pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
|
||||||
|
|
||||||
|
/// Creates a new [`Tick`] wrapping the given value.
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(tick: u32) -> Self {
|
||||||
|
Self { tick }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of this change tick.
|
||||||
|
#[inline]
|
||||||
|
pub const fn get(self) -> u32 {
|
||||||
|
self.tick
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the value of this change tick.
|
||||||
|
#[inline]
|
||||||
|
pub fn set(&mut self, tick: u32) {
|
||||||
|
self.tick = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this `Tick` occurred since the system's `last_run`.
|
||||||
|
///
|
||||||
|
/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
|
||||||
|
// This works even with wraparound because the world tick (`this_run`) is always "newer" than
|
||||||
|
// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
|
||||||
|
// so they never get older than `u32::MAX` (the difference would overflow).
|
||||||
|
//
|
||||||
|
// The clamp here ensures determinism (since scans could differ between app runs).
|
||||||
|
let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
|
||||||
|
let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
|
||||||
|
|
||||||
|
ticks_since_system > ticks_since_insert
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a change tick representing the relationship between `self` and `other`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn relative_to(self, other: Self) -> Self {
|
||||||
|
let tick = self.tick.wrapping_sub(other.tick);
|
||||||
|
Self { tick }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps this change tick's value if it exceeds [`Tick::MAX`].
|
||||||
|
///
|
||||||
|
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||||
|
#[inline]
|
||||||
|
pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
|
||||||
|
let age = check.present_tick().relative_to(*self);
|
||||||
|
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||||
|
// so long as this check always runs before that can happen.
|
||||||
|
if age.get() > Self::MAX.get() {
|
||||||
|
*self = check.present_tick().relative_to(Self::MAX);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
|
||||||
|
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
|
||||||
|
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
|
||||||
|
/// ticks automatically updated via [`World::check_change_ticks`], possibly causing `Tick`-related bugs on
|
||||||
|
/// long-running apps.
|
||||||
|
///
|
||||||
|
/// To fix that, add an observer for this event that calls the schedule's
|
||||||
|
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use bevy_ecs::prelude::*;
|
||||||
|
/// use bevy_ecs::component::CheckChangeTicks;
|
||||||
|
///
|
||||||
|
/// #[derive(Resource)]
|
||||||
|
/// struct CustomSchedule(Schedule);
|
||||||
|
///
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
|
||||||
|
/// schedule.0.check_change_ticks(*check);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, Event)]
|
||||||
|
pub struct CheckChangeTicks(pub(crate) Tick);
|
||||||
|
|
||||||
|
impl CheckChangeTicks {
|
||||||
|
/// Get the present `Tick` that other ticks get compared to.
|
||||||
|
pub fn present_tick(self) -> Tick {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct TickCells<'a> {
|
||||||
|
/// The tick indicating when the value was added to the world.
|
||||||
|
pub added: &'a UnsafeCell<Tick>,
|
||||||
|
/// The tick indicating the last time the value was modified.
|
||||||
|
pub changed: &'a UnsafeCell<Tick>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TickCells<'a> {
|
||||||
|
/// # Safety
|
||||||
|
/// All cells contained within must uphold the safety invariants of [`UnsafeCellDeref::read`].
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn read(&self) -> ComponentTicks {
|
||||||
|
ComponentTicks {
|
||||||
|
// SAFETY: The callers uphold the invariants for `read`.
|
||||||
|
added: unsafe { self.added.read() },
|
||||||
|
// SAFETY: The callers uphold the invariants for `read`.
|
||||||
|
changed: unsafe { self.changed.read() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
|
||||||
|
pub struct ComponentTicks {
|
||||||
|
/// Tick recording the time this component or resource was added.
|
||||||
|
pub added: Tick,
|
||||||
|
|
||||||
|
/// Tick recording the time this component or resource was most recently changed.
|
||||||
|
pub changed: Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentTicks {
|
||||||
|
/// Returns `true` if the component or resource was added after the system last ran
|
||||||
|
/// (or the system is running for the first time).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||||
|
self.added.is_newer_than(last_run, this_run)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran
|
||||||
|
/// (or the system is running for the first time).
|
||||||
|
#[inline]
|
||||||
|
pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||||
|
self.changed.is_newer_than(last_run, this_run)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new instance with the same change tick for `added` and `changed`.
|
||||||
|
pub fn new(change_tick: Tick) -> Self {
|
||||||
|
Self {
|
||||||
|
added: change_tick,
|
||||||
|
changed: change_tick,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manually sets the change tick.
|
||||||
|
///
|
||||||
|
/// This is normally done automatically via the [`DerefMut`] implementation
|
||||||
|
/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
|
||||||
|
/// However, components and resources that make use of interior mutability might require manual updates.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```no_run
|
||||||
|
/// # use bevy_ecs::{world::World, component::ComponentTicks};
|
||||||
|
/// let world: World = unimplemented!();
|
||||||
|
/// let component_ticks: ComponentTicks = unimplemented!();
|
||||||
|
///
|
||||||
|
/// component_ticks.set_changed(world.read_change_tick());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn set_changed(&mut self, change_tick: Tick) {
|
||||||
|
self.changed = change_tick;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user