use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource};
use std::ops::{Deref, DerefMut};
pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000;
pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
pub trait DetectChanges {
type Inner: ?Sized;
fn is_added(&self) -> bool;
fn is_changed(&self) -> bool;
fn set_changed(&mut self);
fn last_changed(&self) -> u32;
fn set_last_changed(&mut self, last_change_tick: u32);
fn bypass_change_detection(&mut self) -> &mut Self::Inner;
}
macro_rules! change_detection_impl {
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
impl<$($generics),* : ?Sized $(+ $traits)?> DetectChanges for $name<$($generics),*> {
type Inner = $target;
#[inline]
fn is_added(&self) -> bool {
self.ticks
.component_ticks
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
}
#[inline]
fn is_changed(&self) -> bool {
self.ticks
.component_ticks
.is_changed(self.ticks.last_change_tick, self.ticks.change_tick)
}
#[inline]
fn set_changed(&mut self) {
self.ticks
.component_ticks
.set_changed(self.ticks.change_tick);
}
#[inline]
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}
#[inline]
fn set_last_changed(&mut self, last_change_tick: u32) {
self.ticks.last_change_tick = last_change_tick
}
#[inline]
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
self.value
}
}
impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> {
type Target = $target;
#[inline]
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<$($generics),* : ?Sized $(+ $traits)?> DerefMut for $name<$($generics),*> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.set_changed();
self.value
}
}
impl<$($generics),* $(: $traits)?> AsRef<$target> for $name<$($generics),*> {
#[inline]
fn as_ref(&self) -> &$target {
self.deref()
}
}
impl<$($generics),* $(: $traits)?> AsMut<$target> for $name<$($generics),*> {
#[inline]
fn as_mut(&mut self) -> &mut $target {
self.deref_mut()
}
}
};
}
macro_rules! impl_methods {
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
impl<$($generics),* : ?Sized $(+ $traits)?> $name<$($generics),*> {
#[inline]
pub fn into_inner(mut self) -> &'a mut $target {
self.set_changed();
self.value
}
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'a, U> {
Mut {
value: f(self.value),
ticks: self.ticks,
}
}
}
};
}
macro_rules! impl_debug {
($name:ident < $( $generics:tt ),+ >, $($traits:ident)?) => {
impl<$($generics),* : ?Sized $(+ $traits)?> std::fmt::Debug for $name<$($generics),*>
where T: std::fmt::Debug
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple(stringify!($name))
.field(&self.value)
.finish()
}
}
};
}
pub(crate) struct Ticks<'a> {
pub(crate) component_ticks: &'a mut ComponentTicks,
pub(crate) last_change_tick: u32,
pub(crate) change_tick: u32,
}
pub struct ResMut<'a, T: ?Sized + Resource> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
}
impl<'w, 'a, T: Resource> IntoIterator for &'a ResMut<'w, T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}
impl<'w, 'a, T: Resource> IntoIterator for &'a mut ResMut<'w, T>
where
&'a mut T: IntoIterator,
{
type Item = <&'a mut T as IntoIterator>::Item;
type IntoIter = <&'a mut T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.set_changed();
self.value.into_iter()
}
}
change_detection_impl!(ResMut<'a, T>, T, Resource);
impl_methods!(ResMut<'a, T>, T, Resource);
impl_debug!(ResMut<'a, T>, Resource);
impl<'a, T: Resource> From<ResMut<'a, T>> for Mut<'a, T> {
fn from(other: ResMut<'a, T>) -> Mut<'a, T> {
Mut {
value: other.value,
ticks: other.ticks,
}
}
}
pub struct NonSendMut<'a, T: ?Sized + 'static> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
}
change_detection_impl!(NonSendMut<'a, T>, T,);
impl_methods!(NonSendMut<'a, T>, T,);
impl_debug!(NonSendMut<'a, T>,);
impl<'a, T: 'static> From<NonSendMut<'a, T>> for Mut<'a, T> {
fn from(other: NonSendMut<'a, T>) -> Mut<'a, T> {
Mut {
value: other.value,
ticks: other.ticks,
}
}
}
pub struct Mut<'a, T: ?Sized> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
}
impl<'w, 'a, T> IntoIterator for &'a Mut<'w, T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}
impl<'w, 'a, T> IntoIterator for &'a mut Mut<'w, T>
where
&'a mut T: IntoIterator,
{
type Item = <&'a mut T as IntoIterator>::Item;
type IntoIter = <&'a mut T as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.set_changed();
self.value.into_iter()
}
}
change_detection_impl!(Mut<'a, T>, T,);
impl_methods!(Mut<'a, T>, T,);
impl_debug!(Mut<'a, T>,);
pub struct MutUntyped<'a> {
pub(crate) value: PtrMut<'a>,
pub(crate) ticks: Ticks<'a>,
}
impl<'a> MutUntyped<'a> {
#[inline]
pub fn into_inner(self) -> PtrMut<'a> {
self.value
}
}
impl<'a> DetectChanges for MutUntyped<'a> {
type Inner = PtrMut<'a>;
#[inline]
fn is_added(&self) -> bool {
self.ticks
.component_ticks
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
}
#[inline]
fn is_changed(&self) -> bool {
self.ticks
.component_ticks
.is_changed(self.ticks.last_change_tick, self.ticks.change_tick)
}
#[inline]
fn set_changed(&mut self) {
self.ticks
.component_ticks
.set_changed(self.ticks.change_tick);
}
#[inline]
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}
#[inline]
fn set_last_changed(&mut self, last_change_tick: u32) {
self.ticks.last_change_tick = last_change_tick;
}
#[inline]
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
&mut self.value
}
}
impl std::fmt::Debug for MutUntyped<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MutUntyped")
.field(&self.value.as_ptr())
.finish()
}
}
#[cfg(test)]
mod tests {
use bevy_ecs_macros::Resource;
use crate::{
self as bevy_ecs,
change_detection::{
ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE,
},
component::Component,
query::ChangeTrackers,
system::{IntoSystem, Query, System},
world::World,
};
#[derive(Component)]
struct C;
#[derive(Resource)]
struct R;
#[test]
fn change_expiration() {
fn change_detected(query: Query<ChangeTrackers<C>>) -> bool {
query.single().is_changed()
}
fn change_expired(query: Query<ChangeTrackers<C>>) -> bool {
query.single().is_changed()
}
let mut world = World::new();
world.spawn(C);
let mut change_detected_system = IntoSystem::into_system(change_detected);
let mut change_expired_system = IntoSystem::into_system(change_expired);
change_detected_system.initialize(&mut world);
change_expired_system.initialize(&mut world);
assert!(change_detected_system.run((), &mut world));
let change_tick = world.change_tick.get_mut();
*change_tick = change_tick.wrapping_add(MAX_CHANGE_AGE);
assert!(!change_expired_system.run((), &mut world));
}
#[test]
fn change_tick_wraparound() {
fn change_detected(query: Query<ChangeTrackers<C>>) -> bool {
query.single().is_changed()
}
let mut world = World::new();
world.last_change_tick = u32::MAX;
*world.change_tick.get_mut() = 0;
world.spawn(C);
let mut change_detected_system = IntoSystem::into_system(change_detected);
change_detected_system.initialize(&mut world);
assert!(change_detected_system.run((), &mut world));
}
#[test]
fn change_tick_scan() {
let mut world = World::new();
world.spawn(C);
*world.change_tick.get_mut() += MAX_CHANGE_AGE + CHECK_TICK_THRESHOLD;
let change_tick = world.change_tick();
let mut query = world.query::<ChangeTrackers<C>>();
for tracker in query.iter(&world) {
let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added);
let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed);
assert!(ticks_since_insert > MAX_CHANGE_AGE);
assert!(ticks_since_change > MAX_CHANGE_AGE);
}
world.check_change_ticks();
for tracker in query.iter(&world) {
let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added);
let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed);
assert!(ticks_since_insert == MAX_CHANGE_AGE);
assert!(ticks_since_change == MAX_CHANGE_AGE);
}
}
#[test]
fn mut_from_res_mut() {
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
};
let ticks = Ticks {
component_ticks: &mut component_ticks,
last_change_tick: 3,
change_tick: 4,
};
let mut res = R {};
let res_mut = ResMut {
value: &mut res,
ticks,
};
let into_mut: Mut<R> = res_mut.into();
assert_eq!(1, into_mut.ticks.component_ticks.added);
assert_eq!(2, into_mut.ticks.component_ticks.changed);
assert_eq!(3, into_mut.ticks.last_change_tick);
assert_eq!(4, into_mut.ticks.change_tick);
}
#[test]
fn mut_from_non_send_mut() {
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
};
let ticks = Ticks {
component_ticks: &mut component_ticks,
last_change_tick: 3,
change_tick: 4,
};
let mut res = R {};
let non_send_mut = NonSendMut {
value: &mut res,
ticks,
};
let into_mut: Mut<R> = non_send_mut.into();
assert_eq!(1, into_mut.ticks.component_ticks.added);
assert_eq!(2, into_mut.ticks.component_ticks.changed);
assert_eq!(3, into_mut.ticks.last_change_tick);
assert_eq!(4, into_mut.ticks.change_tick);
}
#[test]
fn map_mut() {
use super::*;
struct Outer(i64);
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
};
let (last_change_tick, change_tick) = (2, 3);
let ticks = Ticks {
component_ticks: &mut component_ticks,
last_change_tick,
change_tick,
};
let mut outer = Outer(0);
let ptr = Mut {
value: &mut outer,
ticks,
};
assert!(!ptr.is_changed());
let mut inner = ptr.map_unchanged(|x| &mut x.0);
assert!(!inner.is_changed());
*inner = 64;
assert!(inner.is_changed());
assert!(component_ticks.is_changed(last_change_tick, change_tick));
}
}