Introduce CheckChangeTicks
event that is triggered by World::check_change_ticks
(#19274)
# Objective In the past I had custom data structures containing `Tick`s. I learned that these need to be regularly checked to clamp them. But there was no way to hook into that logic so I abandoned storing ticks since then. Another motivation to open this up some more is to be more able to do a correct implementation of `System::check_ticks`. ## Solution Add `CheckChangeTicks` and trigger it in `World::check_change_ticks`. Make `Tick::check_tick` public. This event makes it possible to store ticks in components or resources and have them checked. I also made `Schedules::check_change_ticks` public so users can store schedules in custom resources/components for whatever reasons. ## Testing The logic boils down to a single `World::trigger` call and I don't think this needs more tests. ## Alternatives Making this obsolete like with #15683. --- ## Showcase From the added docs: ```rs use bevy_ecs::prelude::*; use bevy_ecs::component::CheckChangeTicks; #[derive(Resource)] struct CustomSchedule(Schedule); let mut world = World::new(); world.add_observer(|tick: Trigger<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| { schedule.0.check_change_ticks(tick.get()); }); ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
2768af5d2d
commit
1294b71e35
@ -15,6 +15,7 @@ use crate::{
|
||||
use alloc::boxed::Box;
|
||||
use alloc::{borrow::Cow, format, vec::Vec};
|
||||
pub use bevy_ecs_macros::Component;
|
||||
use bevy_ecs_macros::Event;
|
||||
use bevy_platform::sync::Arc;
|
||||
use bevy_platform::{
|
||||
collections::{HashMap, HashSet},
|
||||
@ -2616,7 +2617,7 @@ impl Tick {
|
||||
///
|
||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||
#[inline]
|
||||
pub(crate) fn check_tick(&mut self, tick: Tick) -> bool {
|
||||
pub fn check_tick(&mut self, tick: Tick) -> bool {
|
||||
let age = 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.
|
||||
@ -2629,6 +2630,41 @@ impl Tick {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(|tick: Trigger<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
|
||||
/// schedule.0.check_change_ticks(tick.get());
|
||||
/// });
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Event)]
|
||||
pub struct CheckChangeTicks(pub(crate) Tick);
|
||||
|
||||
impl CheckChangeTicks {
|
||||
/// Get the `Tick` that can be used as the parameter of [`Tick::check_tick`].
|
||||
pub fn get(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> {
|
||||
|
@ -558,7 +558,7 @@ impl Schedule {
|
||||
/// Iterates the change ticks of all systems in the schedule and clamps any older than
|
||||
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
||||
/// This prevents overflow and thus prevents false positives.
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
pub fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for system in &mut self.executable.systems {
|
||||
if !is_apply_deferred(system) {
|
||||
system.check_change_tick(change_tick);
|
||||
|
@ -39,9 +39,9 @@ use crate::{
|
||||
},
|
||||
change_detection::{MaybeLocation, MutUntyped, TicksMut},
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentIds, ComponentInfo,
|
||||
ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable,
|
||||
RequiredComponents, RequiredComponentsError, Tick,
|
||||
CheckChangeTicks, Component, ComponentDescriptor, ComponentHooks, ComponentId,
|
||||
ComponentIds, ComponentInfo, ComponentTicks, Components, ComponentsQueuedRegistrator,
|
||||
ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, Tick,
|
||||
},
|
||||
entity::{Entities, Entity, EntityDoesNotExistError},
|
||||
entity_disabling::DefaultQueryFilters,
|
||||
@ -2964,6 +2964,8 @@ impl World {
|
||||
schedules.check_change_ticks(change_tick);
|
||||
}
|
||||
|
||||
self.trigger(CheckChangeTicks(change_tick));
|
||||
|
||||
self.last_check_tick = change_tick;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user