Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 91 additions & 72 deletions crates/bevy_ecs/src/change_detection.rs

Large diffs are not rendered by default.

25 changes: 6 additions & 19 deletions crates/bevy_ecs/src/component/tick.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use bevy_ecs_macros::Event;
use bevy_ptr::UnsafeCellDeref;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use core::cell::UnsafeCell;
use core::{cell::UnsafeCell, panic::Location};

use crate::change_detection::MAX_CHANGE_AGE;
use crate::change_detection::{MaybeLocation, MAX_CHANGE_AGE};

/// A value that tracks when a system ran relative to other systems.
/// This is used to power change detection.
Expand Down Expand Up @@ -121,27 +120,15 @@ impl CheckChangeTicks {
}
}

/// Interior-mutable access to the [`Tick`]s for a single component or resource.
/// Interior-mutable access to the [`Tick`]s of a single component or resource.
#[derive(Copy, Clone, Debug)]
pub struct TickCells<'a> {
pub struct ComponentTickCells<'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() },
}
}
/// The calling location that last modified the value.
pub changed_by: MaybeLocation<&'a UnsafeCell<&'static Location<'static>>>,
}

/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
Expand Down
28 changes: 17 additions & 11 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
archetype::{Archetype, Archetypes},
bundle::Bundle,
change_detection::{MaybeLocation, Ticks, TicksMut},
change_detection::{ComponentTicksMut, ComponentTicksRef, MaybeLocation},
component::{Component, ComponentId, Components, Mutable, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
Expand Down Expand Up @@ -1801,18 +1801,18 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> {

Ref {
value: component.deref(),
ticks: Ticks {
ticks: ComponentTicksRef {
added: added.deref(),
changed: changed.deref(),
changed_by: caller.map(|caller| caller.deref()),
this_run: fetch.this_run,
last_run: fetch.last_run,
},
changed_by: caller.map(|caller| caller.deref()),
}
},
|sparse_set| {
// SAFETY: The caller ensures `entity` is in range and has the component.
let (component, ticks, caller) = unsafe {
let (component, ticks) = unsafe {
sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
Expand All @@ -1821,8 +1821,11 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> {

Ref {
value: component.deref(),
ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
changed_by: caller.map(|caller| caller.deref()),
ticks: ComponentTicksRef::from_tick_cells(
ticks,
fetch.last_run,
fetch.this_run,
),
}
},
)
Expand Down Expand Up @@ -2007,18 +2010,18 @@ unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for &'__w mut T

Mut {
value: component.deref_mut(),
ticks: TicksMut {
ticks: ComponentTicksMut {
added: added.deref_mut(),
changed: changed.deref_mut(),
changed_by: caller.map(|caller| caller.deref_mut()),
this_run: fetch.this_run,
last_run: fetch.last_run,
},
changed_by: caller.map(|caller| caller.deref_mut()),
}
},
|sparse_set| {
// SAFETY: The caller ensures `entity` is in range and has the component.
let (component, ticks, caller) = unsafe {
let (component, ticks) = unsafe {
sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
Expand All @@ -2027,8 +2030,11 @@ unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for &'__w mut T

Mut {
value: component.assert_unique().deref_mut(),
ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
changed_by: caller.map(|caller| caller.deref_mut()),
ticks: ComponentTicksMut::from_tick_cells(
ticks,
fetch.last_run,
fetch.this_run,
),
}
},
)
Expand Down
24 changes: 9 additions & 15 deletions crates/bevy_ecs/src/storage/resource.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::{
change_detection::{MaybeLocation, MutUntyped, TicksMut},
component::{CheckChangeTicks, ComponentId, ComponentTicks, Components, Tick, TickCells},
change_detection::{ComponentTicksMut, MaybeLocation, MutUntyped},
component::{
CheckChangeTicks, ComponentId, ComponentTickCells, ComponentTicks, Components, Tick,
},
storage::{blob_array::BlobArray, SparseSet},
};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
Expand Down Expand Up @@ -124,23 +126,17 @@ impl<const SEND: bool> ResourceData<SEND> {
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
/// original thread it was inserted in.
#[inline]
pub(crate) fn get_with_ticks(
&self,
) -> Option<(
Ptr<'_>,
TickCells<'_>,
MaybeLocation<&UnsafeCell<&'static Location<'static>>>,
)> {
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> {
self.is_present().then(|| {
self.validate_access();
(
// SAFETY: We've already checked if a value is present, and there should only be one.
unsafe { self.data.get_unchecked(Self::ROW) },
TickCells {
ComponentTickCells {
added: &self.added_ticks,
changed: &self.changed_ticks,
changed_by: self.changed_by.as_ref(),
},
self.changed_by.as_ref(),
)
})
}
Expand All @@ -151,14 +147,12 @@ impl<const SEND: bool> ResourceData<SEND> {
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
/// original thread it was inserted in.
pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {
let (ptr, ticks, caller) = self.get_with_ticks()?;
let (ptr, ticks) = self.get_with_ticks()?;
Some(MutUntyped {
// SAFETY: We have exclusive access to the underlying storage.
value: unsafe { ptr.assert_unique() },
// SAFETY: We have exclusive access to the underlying storage.
ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) },
// SAFETY: We have exclusive access to the underlying storage.
changed_by: unsafe { caller.map(|caller| caller.deref_mut()) },
ticks: unsafe { ComponentTicksMut::from_tick_cells(ticks, last_run, this_run) },
})
}

Expand Down
17 changes: 6 additions & 11 deletions crates/bevy_ecs/src/storage/sparse_set.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::{
change_detection::MaybeLocation,
component::{CheckChangeTicks, ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells},
component::{
CheckChangeTicks, ComponentId, ComponentInfo, ComponentTickCells, ComponentTicks, Tick,
},
entity::{Entity, EntityRow},
query::DebugCheckedUnwrap,
storage::{AbortOnPanic, Column, TableRow, VecExtensions},
Expand Down Expand Up @@ -252,26 +254,19 @@ impl ComponentSparseSet {
///
/// Returns `None` if `entity` does not have a component in the sparse set.
#[inline]
pub fn get_with_ticks(
&self,
entity: Entity,
) -> Option<(
Ptr<'_>,
TickCells<'_>,
MaybeLocation<&UnsafeCell<&'static Location<'static>>>,
)> {
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> {
let dense_index = *self.sparse.get(entity.row())?;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index.index()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe {
Some((
self.dense.get_data_unchecked(dense_index),
TickCells {
ComponentTickCells {
added: self.dense.get_added_tick_unchecked(dense_index),
changed: self.dense.get_changed_tick_unchecked(dense_index),
changed_by: self.dense.get_changed_by_unchecked(dense_index),
},
self.dense.get_changed_by_unchecked(dense_index),
))
}
}
Expand Down
Loading