Split Component Ticks (#6547)

# Objective
Fixes #4884. `ComponentTicks` stores both added and changed ticks contiguously in the same 8 bytes. This is convenient when passing around both together, but causes half the bytes fetched from memory for the purposes of change detection to effectively go unused. This is inefficient when most queries (no filter, mutating *something*) only write out to the changed ticks.

## Solution
Split the storage for change detection ticks into two separate `Vec`s inside `Column`. Fetch only what is needed during iteration.

This also potentially also removes one blocker from autovectorization of dense queries.

EDIT: This is confirmed to enable autovectorization of dense queries in `for_each` and `par_for_each`  where possible.  Unfortunately `iter` has other blockers that prevent it.

### TODO

 - [x] Microbenchmark
 - [x] Check if this allows query iteration to autovectorize simple loops.
 - [x] Clean up all of the spurious tuples now littered throughout the API

### Open Questions

 - ~~Is `Mut::is_added` absolutely necessary? Can we not just use `Added` or `ChangeTrackers`?~~ It's optimized out if unused.
 - ~~Does the fetch of the added ticks get optimized out if not used?~~ Yes it is.

---

## Changelog
Added: `Tick`, a wrapper around a single change detection tick.
Added: `Column::get_added_ticks`
Added: `Column::get_column_ticks`
Added: `SparseSet::get_added_ticks`
Added: `SparseSet::get_column_ticks`
Changed: `Column` now stores added and changed ticks separately internally.
Changed: Most APIs returning `&UnsafeCell<ComponentTicks>` now returns `TickCells` instead, which contains two separate `&UnsafeCell<Tick>` for either component ticks.
Changed: `Query::for_each(_mut)`, `Query::par_for_each(_mut)` will now leverage autovectorization to speed up query iteration where possible.

## Migration Guide
TODO
This commit is contained in:
James Liu 2022-11-21 12:59:09 +00:00
parent 210979f631
commit 55ca7fc88e
11 changed files with 390 additions and 262 deletions

View File

@ -9,7 +9,7 @@ use crate::{
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
SpawnBundleStatus,
},
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
storage::{SparseSetIndex, SparseSets, Storages, Table},
};
@ -394,11 +394,7 @@ impl BundleInfo {
// SAFETY: bundle_component is a valid index for this bundle
match bundle_component_status.get_status(bundle_component) {
ComponentStatus::Added => {
column.initialize(
table_row,
component_ptr,
ComponentTicks::new(change_tick),
);
column.initialize(table_row, component_ptr, Tick::new(change_tick));
}
ComponentStatus::Mutated => {
column.replace(table_row, component_ptr, change_tick);

View File

@ -1,6 +1,11 @@
//! Types that detect when their internal data mutate.
use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource};
use crate::{
component::{Tick, TickCells},
ptr::PtrMut,
system::Resource,
};
use bevy_ptr::UnsafeCellDeref;
use std::ops::{Deref, DerefMut};
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
@ -95,21 +100,21 @@ macro_rules! change_detection_impl {
#[inline]
fn is_added(&self) -> bool {
self.ticks
.component_ticks
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
.added
.is_older_than(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)
.changed
.is_older_than(self.ticks.last_change_tick, self.ticks.change_tick)
}
#[inline]
fn set_changed(&mut self) {
self.ticks
.component_ticks
.changed
.set_changed(self.ticks.change_tick);
}
@ -224,11 +229,30 @@ macro_rules! impl_debug {
}
pub(crate) struct Ticks<'a> {
pub(crate) component_ticks: &'a mut ComponentTicks,
pub(crate) added: &'a mut Tick,
pub(crate) changed: &'a mut Tick,
pub(crate) last_change_tick: u32,
pub(crate) change_tick: u32,
}
impl<'a> Ticks<'a> {
/// # Safety
/// This should never alias the underlying ticks. All access must be unique.
#[inline]
pub(crate) unsafe fn from_tick_cells(
cells: TickCells<'a>,
last_change_tick: u32,
change_tick: u32,
) -> Self {
Self {
added: cells.added.deref_mut(),
changed: cells.changed.deref_mut(),
last_change_tick,
change_tick,
}
}
}
/// Unique mutable borrow of a [`Resource`].
///
/// See the [`Resource`] documentation for usage.
@ -381,22 +405,20 @@ impl<'a> DetectChanges for MutUntyped<'a> {
#[inline]
fn is_added(&self) -> bool {
self.ticks
.component_ticks
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
.added
.is_older_than(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)
.changed
.is_older_than(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);
self.ticks.changed.set_changed(self.ticks.change_tick);
}
#[inline]
@ -429,10 +451,8 @@ mod tests {
use crate::{
self as bevy_ecs,
change_detection::{
ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE,
},
component::Component,
change_detection::{Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE},
component::{Component, ComponentTicks, Tick},
query::ChangeTrackers,
system::{IntoSystem, Query, System},
world::World,
@ -514,8 +534,8 @@ mod tests {
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);
let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick);
let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick);
assert!(ticks_since_insert > MAX_CHANGE_AGE);
assert!(ticks_since_change > MAX_CHANGE_AGE);
}
@ -524,8 +544,8 @@ mod tests {
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);
let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick);
let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick);
assert!(ticks_since_insert == MAX_CHANGE_AGE);
assert!(ticks_since_change == MAX_CHANGE_AGE);
}
@ -534,11 +554,12 @@ mod tests {
#[test]
fn mut_from_res_mut() {
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
component_ticks: &mut component_ticks,
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick: 3,
change_tick: 4,
};
@ -549,8 +570,8 @@ mod tests {
};
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!(1, into_mut.ticks.added.tick);
assert_eq!(2, into_mut.ticks.changed.tick);
assert_eq!(3, into_mut.ticks.last_change_tick);
assert_eq!(4, into_mut.ticks.change_tick);
}
@ -558,11 +579,12 @@ mod tests {
#[test]
fn mut_from_non_send_mut() {
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
component_ticks: &mut component_ticks,
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick: 3,
change_tick: 4,
};
@ -573,8 +595,8 @@ mod tests {
};
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!(1, into_mut.ticks.added.tick);
assert_eq!(2, into_mut.ticks.changed.tick);
assert_eq!(3, into_mut.ticks.last_change_tick);
assert_eq!(4, into_mut.ticks.change_tick);
}
@ -584,13 +606,14 @@ mod tests {
use super::*;
struct Outer(i64);
let mut component_ticks = ComponentTicks {
added: 1,
changed: 2,
};
let (last_change_tick, change_tick) = (2, 3);
let mut component_ticks = ComponentTicks {
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
component_ticks: &mut component_ticks,
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick,
change_tick,
};

View File

@ -6,7 +6,8 @@ use crate::{
system::Resource,
};
pub use bevy_ecs_macros::Component;
use bevy_ptr::OwningPtr;
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
use std::cell::UnsafeCell;
use std::{
alloc::Layout,
any::{Any, TypeId},
@ -517,23 +518,26 @@ impl Components {
}
}
/// Records when a component was added and when it was last mutably dereferenced (or added).
/// Used to track changes in state between system runs, e.g. components being added or accessed mutably.
#[derive(Copy, Clone, Debug)]
pub struct ComponentTicks {
pub(crate) added: u32,
pub(crate) changed: u32,
pub struct Tick {
pub(crate) tick: u32,
}
impl ComponentTicks {
impl Tick {
pub const fn new(tick: u32) -> Self {
Self { tick }
}
#[inline]
/// Returns `true` if the component was added after the system last ran.
pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool {
/// Returns `true` if the tick is older than the system last's run.
pub fn is_older_than(&self, last_change_tick: u32, change_tick: u32) -> bool {
// This works even with wraparound because the world tick (`change_tick`) is always "newer" than
// `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values
// `last_change_tick` 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 = change_tick.wrapping_sub(self.added).min(MAX_CHANGE_AGE);
let ticks_since_insert = change_tick.wrapping_sub(self.tick).min(MAX_CHANGE_AGE);
let ticks_since_system = change_tick
.wrapping_sub(last_change_tick)
.min(MAX_CHANGE_AGE);
@ -541,34 +545,15 @@ impl ComponentTicks {
ticks_since_system > ticks_since_insert
}
#[inline]
/// Returns `true` if the component was added or mutably dereferenced after the system last ran.
pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool {
// This works even with wraparound because the world tick (`change_tick`) is always "newer" than
// `last_change_tick` and `self.changed`, 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_change = change_tick.wrapping_sub(self.changed).min(MAX_CHANGE_AGE);
let ticks_since_system = change_tick
.wrapping_sub(last_change_tick)
.min(MAX_CHANGE_AGE);
ticks_since_system > ticks_since_change
}
pub(crate) fn new(change_tick: u32) -> Self {
Self {
added: change_tick,
changed: change_tick,
pub(crate) fn check_tick(&mut self, change_tick: u32) {
let age = change_tick.wrapping_sub(self.tick);
// 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 > MAX_CHANGE_AGE {
self.tick = change_tick.wrapping_sub(MAX_CHANGE_AGE);
}
}
pub(crate) fn check_ticks(&mut self, change_tick: u32) {
check_tick(&mut self.added, change_tick);
check_tick(&mut self.changed, change_tick);
}
/// Manually sets the change tick.
///
/// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation
@ -585,15 +570,72 @@ impl ComponentTicks {
/// ```
#[inline]
pub fn set_changed(&mut self, change_tick: u32) {
self.changed = change_tick;
self.tick = change_tick;
}
}
fn check_tick(last_change_tick: &mut u32, change_tick: u32) {
let age = change_tick.wrapping_sub(*last_change_tick);
// 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 > MAX_CHANGE_AGE {
*last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE);
/// Wrapper around [`Tick`]s for a single component
#[derive(Copy, Clone, Debug)]
pub struct TickCells<'a> {
pub added: &'a UnsafeCell<Tick>,
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 {
added: self.added.read(),
changed: self.changed.read(),
}
}
}
/// Records when a component was added and when it was last mutably dereferenced (or added).
#[derive(Copy, Clone, Debug)]
pub struct ComponentTicks {
pub(crate) added: Tick,
pub(crate) changed: Tick,
}
impl ComponentTicks {
#[inline]
/// Returns `true` if the component was added after the system last ran.
pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool {
self.added.is_older_than(last_change_tick, change_tick)
}
#[inline]
/// Returns `true` if the component was added or mutably dereferenced after the system last ran.
pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool {
self.changed.is_older_than(last_change_tick, change_tick)
}
pub(crate) fn new(change_tick: u32) -> Self {
Self {
added: Tick::new(change_tick),
changed: Tick::new(change_tick),
}
}
/// Manually sets the change tick.
///
/// This is normally done automatically via the [`DerefMut`](std::ops::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
/// ```rust,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: u32) {
self.changed.set_changed(change_tick);
}
}

View File

@ -1,7 +1,7 @@
use crate::{
archetype::{Archetype, ArchetypeComponentId},
change_detection::Ticks,
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType},
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick},
entity::Entity,
query::{Access, DebugCheckedUnwrap, FilteredAccess},
storage::{ComponentSparseSet, Table},
@ -654,7 +654,8 @@ pub struct WriteFetch<'w, T> {
// T::Storage = TableStorage
table_data: Option<(
ThinSlicePtr<'w, UnsafeCell<T>>,
ThinSlicePtr<'w, UnsafeCell<ComponentTicks>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>,
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,
@ -733,7 +734,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some((
column.get_data_slice().into(),
column.get_ticks_slice().into(),
column.get_added_ticks_slice().into(),
column.get_changed_ticks_slice().into(),
));
}
@ -745,29 +747,27 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, table_ticks) = fetch.table_data.debug_checked_unwrap();
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Mut {
value: table_components.get(table_row).deref_mut(),
ticks: Ticks {
component_ticks: table_ticks.get(table_row).deref_mut(),
added: added_ticks.get(table_row).deref_mut(),
changed: changed_ticks.get(table_row).deref_mut(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
}
}
StorageType::SparseSet => {
let (component, component_ticks) = fetch
let (component, ticks) = fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap();
Mut {
value: component.assert_unique().deref_mut(),
ticks: Ticks {
component_ticks: component_ticks.deref_mut(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
ticks: Ticks::from_tick_cells(ticks, fetch.change_tick, fetch.last_change_tick),
}
}
}
@ -992,7 +992,8 @@ impl<T: Component> ChangeTrackers<T> {
#[doc(hidden)]
pub struct ChangeTrackersFetch<'w, T> {
// T::Storage = TableStorage
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<ComponentTicks>>>,
table_added: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
table_changed: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,
@ -1028,7 +1029,8 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
change_tick: u32,
) -> ChangeTrackersFetch<'w, T> {
ChangeTrackersFetch {
table_ticks: None,
table_added: None,
table_changed: None,
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| {
world
.storages()
@ -1044,7 +1046,8 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
ChangeTrackersFetch {
table_ticks: fetch.table_ticks,
table_added: fetch.table_added,
table_changed: fetch.table_changed,
sparse_set: fetch.sparse_set,
marker: fetch.marker,
last_change_tick: fetch.last_change_tick,
@ -1070,13 +1073,9 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
&id: &ComponentId,
table: &'w Table,
) {
fetch.table_ticks = Some(
table
.get_column(id)
.debug_checked_unwrap()
.get_ticks_slice()
.into(),
);
let column = table.get_column(id).debug_checked_unwrap();
fetch.table_added = Some(column.get_added_ticks_slice().into());
fetch.table_changed = Some(column.get_changed_ticks_slice().into());
}
#[inline(always)]
@ -1088,20 +1087,29 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => ChangeTrackers {
component_ticks: {
let table_ticks = fetch.table_ticks.debug_checked_unwrap();
table_ticks.get(table_row).read()
ComponentTicks {
added: fetch
.table_added
.debug_checked_unwrap()
.get(table_row)
.read(),
changed: fetch
.table_changed
.debug_checked_unwrap()
.get(table_row)
.read(),
}
},
marker: PhantomData,
last_change_tick: fetch.last_change_tick,
change_tick: fetch.change_tick,
},
StorageType::SparseSet => ChangeTrackers {
component_ticks: *fetch
component_ticks: fetch
.sparse_set
.debug_checked_unwrap()
.get_ticks(entity)
.debug_checked_unwrap()
.get(),
.debug_checked_unwrap(),
marker: PhantomData,
last_change_tick: fetch.last_change_tick,
change_tick: fetch.change_tick,

View File

@ -1,9 +1,9 @@
use crate::{
archetype::{Archetype, ArchetypeComponentId},
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType},
component::{Component, ComponentId, ComponentStorage, StorageType, Tick},
entity::Entity,
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{ComponentSparseSet, Table},
storage::{Column, ComponentSparseSet, Table},
world::World,
};
use bevy_ecs_macros::all_tuples;
@ -405,7 +405,8 @@ macro_rules! impl_tick_filter {
$name: ident,
$(#[$fetch_meta:meta])*
$fetch_name: ident,
$is_detected: expr
$get_slice: expr,
$get_sparse_set: expr
) => {
$(#[$meta])*
pub struct $name<T>(PhantomData<T>);
@ -413,7 +414,7 @@ macro_rules! impl_tick_filter {
#[doc(hidden)]
$(#[$fetch_meta])*
pub struct $fetch_name<'w, T> {
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<ComponentTicks>>>,
table_ticks: Option< ThinSlicePtr<'w, UnsafeCell<Tick>>>,
marker: PhantomData<T>,
sparse_set: Option<&'w ComponentSparseSet>,
last_change_tick: u32,
@ -475,10 +476,11 @@ macro_rules! impl_tick_filter {
table: &'w Table
) {
fetch.table_ticks = Some(
table.get_column(component_id)
.debug_checked_unwrap()
.get_ticks_slice()
.into()
$get_slice(
&table
.get_column(component_id)
.debug_checked_unwrap()
).into(),
);
}
@ -502,23 +504,21 @@ macro_rules! impl_tick_filter {
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
$is_detected(&*(
fetch.table_ticks
fetch
.table_ticks
.debug_checked_unwrap()
.get(table_row))
.deref(),
fetch.last_change_tick,
fetch.change_tick
)
.get(table_row)
.deref()
.is_older_than(fetch.last_change_tick, fetch.change_tick)
}
StorageType::SparseSet => {
let ticks = &*fetch
let sparse_set = &fetch
.sparse_set
.debug_checked_unwrap();
$get_sparse_set(sparse_set, entity)
.debug_checked_unwrap()
.get_ticks(entity)
.debug_checked_unwrap()
.get();
$is_detected(ticks, fetch.last_change_tick, fetch.change_tick)
.deref()
.is_older_than(fetch.last_change_tick, fetch.change_tick)
}
}
}
@ -595,7 +595,8 @@ impl_tick_filter!(
/// ```
Added,
AddedFetch,
ComponentTicks::is_added
Column::get_added_ticks_slice,
ComponentSparseSet::get_added_ticks
);
impl_tick_filter!(
@ -632,7 +633,8 @@ impl_tick_filter!(
/// ```
Changed,
ChangedFetch,
ComponentTicks::is_changed
Column::get_changed_ticks_slice,
ComponentSparseSet::get_changed_ticks
);
/// A marker trait to indicate that the filter works at an archetype level.

View File

@ -1,8 +1,7 @@
use crate::archetype::ArchetypeComponentId;
use crate::component::{ComponentId, ComponentTicks, Components};
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
use crate::storage::{Column, SparseSet};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
use std::cell::UnsafeCell;
/// The type-erased backing storage and metadata for a single resource within a [`World`].
///
@ -33,18 +32,12 @@ impl ResourceData {
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
#[inline]
pub fn get_ticks(&self) -> Option<&ComponentTicks> {
self.column
.get_ticks(0)
// SAFETY:
// - This borrow's lifetime is bounded by the lifetime on self.
// - A read-only borrow on self can only exist while a mutable borrow doesn't
// exist.
.map(|ticks| unsafe { ticks.deref() })
pub fn get_ticks(&self) -> Option<ComponentTicks> {
self.column.get_ticks(0)
}
#[inline]
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
self.column.get(0)
}
@ -85,7 +78,8 @@ impl ResourceData {
) {
if self.is_present() {
self.column.replace_untracked(0, value);
*self.column.get_ticks_unchecked(0).deref_mut() = change_ticks;
*self.column.get_added_ticks_unchecked(0).deref_mut() = change_ticks.added;
*self.column.get_changed_ticks_unchecked(0).deref_mut() = change_ticks.changed;
} else {
self.column.push(value, change_ticks);
}

View File

@ -1,5 +1,5 @@
use crate::{
component::{ComponentId, ComponentInfo, ComponentTicks},
component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells},
entity::Entity,
storage::Column,
};
@ -189,7 +189,7 @@ impl ComponentSparseSet {
}
#[inline]
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
let dense_index = *self.sparse.get(entity.index())? as usize;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
@ -197,13 +197,34 @@ impl ComponentSparseSet {
unsafe {
Some((
self.dense.get_data_unchecked(dense_index),
self.dense.get_ticks_unchecked(dense_index),
TickCells {
added: self.dense.get_added_ticks_unchecked(dense_index),
changed: self.dense.get_changed_ticks_unchecked(dense_index),
},
))
}
}
#[inline]
pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell<ComponentTicks>> {
pub fn get_added_ticks(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
let dense_index = *self.sparse.get(entity.index())? as usize;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_added_ticks_unchecked(dense_index)) }
}
#[inline]
pub fn get_changed_ticks(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
let dense_index = *self.sparse.get(entity.index())? as usize;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_changed_ticks_unchecked(dense_index)) }
}
#[inline]
pub fn get_ticks(&self, entity: Entity) -> Option<ComponentTicks> {
let dense_index = *self.sparse.get(entity.index())? as usize;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);

View File

@ -1,10 +1,10 @@
use crate::{
component::{ComponentId, ComponentInfo, ComponentTicks, Components},
component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells},
entity::Entity,
query::DebugCheckedUnwrap,
storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet},
};
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref};
use bevy_utils::HashMap;
use std::alloc::Layout;
use std::{
@ -35,7 +35,8 @@ impl TableId {
#[derive(Debug)]
pub struct Column {
data: BlobVec,
ticks: Vec<UnsafeCell<ComponentTicks>>,
added_ticks: Vec<UnsafeCell<Tick>>,
changed_ticks: Vec<UnsafeCell<Tick>>,
}
impl Column {
@ -44,7 +45,8 @@ impl Column {
Column {
// SAFETY: component_info.drop() is valid for the types that will be inserted.
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
ticks: Vec::with_capacity(capacity),
added_ticks: Vec::with_capacity(capacity),
changed_ticks: Vec::with_capacity(capacity),
}
}
@ -60,15 +62,11 @@ impl Column {
/// # Safety
/// Assumes data has already been allocated for the given row.
#[inline]
pub(crate) unsafe fn initialize(
&mut self,
row: usize,
data: OwningPtr<'_>,
ticks: ComponentTicks,
) {
pub(crate) unsafe fn initialize(&mut self, row: usize, data: OwningPtr<'_>, tick: Tick) {
debug_assert!(row < self.len());
self.data.initialize_unchecked(row, data);
*self.ticks.get_unchecked_mut(row).get_mut() = ticks;
*self.added_ticks.get_unchecked_mut(row).get_mut() = tick;
*self.changed_ticks.get_unchecked_mut(row).get_mut() = tick;
}
/// Writes component data to the column at given row.
@ -80,7 +78,7 @@ impl Column {
pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u32) {
debug_assert!(row < self.len());
self.data.replace_unchecked(row, data);
self.ticks
self.changed_ticks
.get_unchecked_mut(row)
.get_mut()
.set_changed(change_tick);
@ -113,7 +111,8 @@ impl Column {
#[inline]
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) {
self.data.swap_remove_and_drop_unchecked(row);
self.ticks.swap_remove(row);
self.added_ticks.swap_remove(row);
self.changed_ticks.swap_remove(row);
}
#[inline]
@ -125,8 +124,9 @@ impl Column {
(row < self.data.len()).then(|| {
// SAFETY: The row was length checked before this.
let data = unsafe { self.data.swap_remove_and_forget_unchecked(row) };
let ticks = self.ticks.swap_remove(row).into_inner();
(data, ticks)
let added = self.added_ticks.swap_remove(row).into_inner();
let changed = self.changed_ticks.swap_remove(row).into_inner();
(data, ComponentTicks { added, changed })
})
}
@ -139,8 +139,9 @@ impl Column {
row: usize,
) -> (OwningPtr<'_>, ComponentTicks) {
let data = self.data.swap_remove_and_forget_unchecked(row);
let ticks = self.ticks.swap_remove(row).into_inner();
(data, ticks)
let added = self.added_ticks.swap_remove(row).into_inner();
let changed = self.changed_ticks.swap_remove(row).into_inner();
(data, ComponentTicks { added, changed })
}
/// Removes the element from `other` at `src_row` and inserts it
@ -164,20 +165,23 @@ impl Column {
debug_assert!(self.data.layout() == other.data.layout());
let ptr = self.data.get_unchecked_mut(dst_row);
other.data.swap_remove_unchecked(src_row, ptr);
*self.ticks.get_unchecked_mut(dst_row) = other.ticks.swap_remove(src_row);
*self.added_ticks.get_unchecked_mut(dst_row) = other.added_ticks.swap_remove(src_row);
*self.changed_ticks.get_unchecked_mut(dst_row) = other.changed_ticks.swap_remove(src_row);
}
// # Safety
// - ptr must point to valid data of this column's component type
pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) {
self.data.push(ptr);
self.ticks.push(UnsafeCell::new(ticks));
self.added_ticks.push(UnsafeCell::new(ticks.added));
self.changed_ticks.push(UnsafeCell::new(ticks.changed));
}
#[inline]
pub(crate) fn reserve_exact(&mut self, additional: usize) {
self.data.reserve_exact(additional);
self.ticks.reserve_exact(additional);
self.added_ticks.reserve_exact(additional);
self.changed_ticks.reserve_exact(additional);
}
#[inline]
@ -192,16 +196,29 @@ impl Column {
}
#[inline]
pub fn get_ticks_slice(&self) -> &[UnsafeCell<ComponentTicks>] {
&self.ticks
pub fn get_added_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
&self.added_ticks
}
#[inline]
pub fn get(&self, row: usize) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
&self.changed_ticks
}
#[inline]
pub fn get(&self, row: usize) -> Option<(Ptr<'_>, TickCells<'_>)> {
(row < self.data.len())
// SAFETY: The row is length checked before fetching the pointer. This is being
// accessed through a read-only reference to the column.
.then(|| unsafe { (self.data.get_unchecked(row), self.ticks.get_unchecked(row)) })
.then(|| unsafe {
(
self.data.get_unchecked(row),
TickCells {
added: self.added_ticks.get_unchecked(row),
changed: self.changed_ticks.get_unchecked(row),
},
)
})
}
#[inline]
@ -237,27 +254,66 @@ impl Column {
}
#[inline]
pub fn get_ticks(&self, row: usize) -> Option<&UnsafeCell<ComponentTicks>> {
self.ticks.get(row)
pub fn get_added_ticks(&self, row: usize) -> Option<&UnsafeCell<Tick>> {
self.added_ticks.get(row)
}
#[inline]
pub fn get_changed_ticks(&self, row: usize) -> Option<&UnsafeCell<Tick>> {
self.changed_ticks.get(row)
}
#[inline]
pub fn get_ticks(&self, row: usize) -> Option<ComponentTicks> {
if row < self.data.len() {
// SAFETY: The size of the column has already been checked.
Some(unsafe { self.get_ticks_unchecked(row) })
} else {
None
}
}
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_ticks_unchecked(&self, row: usize) -> &UnsafeCell<ComponentTicks> {
debug_assert!(row < self.ticks.len());
self.ticks.get_unchecked(row)
pub unsafe fn get_added_ticks_unchecked(&self, row: usize) -> &UnsafeCell<Tick> {
debug_assert!(row < self.added_ticks.len());
self.added_ticks.get_unchecked(row)
}
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_changed_ticks_unchecked(&self, row: usize) -> &UnsafeCell<Tick> {
debug_assert!(row < self.changed_ticks.len());
self.changed_ticks.get_unchecked(row)
}
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_ticks_unchecked(&self, row: usize) -> ComponentTicks {
debug_assert!(row < self.added_ticks.len());
debug_assert!(row < self.changed_ticks.len());
ComponentTicks {
added: self.added_ticks.get_unchecked(row).read(),
changed: self.changed_ticks.get_unchecked(row).read(),
}
}
pub fn clear(&mut self) {
self.data.clear();
self.ticks.clear();
self.added_ticks.clear();
self.changed_ticks.clear();
}
#[inline]
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
for component_ticks in &mut self.ticks {
component_ticks.get_mut().check_ticks(change_tick);
for component_ticks in &mut self.added_ticks {
component_ticks.get_mut().check_tick(change_tick);
}
for component_ticks in &mut self.changed_ticks {
component_ticks.get_mut().check_tick(change_tick);
}
}
}
@ -474,7 +530,8 @@ impl Table {
self.entities.push(entity);
for column in self.columns.values_mut() {
column.data.set_len(self.entities.len());
column.ticks.push(UnsafeCell::new(ComponentTicks::new(0)));
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
}
index
}
@ -631,7 +688,7 @@ mod tests {
use crate::ptr::OwningPtr;
use crate::storage::Storages;
use crate::{
component::{ComponentTicks, Components},
component::{Components, Tick},
entity::Entity,
storage::TableBuilder,
};
@ -657,7 +714,7 @@ mod tests {
table.get_column_mut(component_id).unwrap().initialize(
row,
value_ptr,
ComponentTicks::new(0),
Tick::new(0),
);
});
};

View File

@ -3,7 +3,7 @@ use crate::{
archetype::{Archetype, Archetypes},
bundle::Bundles,
change_detection::Ticks,
component::{Component, ComponentId, ComponentTicks, Components},
component::{Component, ComponentId, ComponentTicks, Components, Tick},
entity::{Entities, Entity},
query::{
Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery,
@ -273,7 +273,8 @@ pub trait Resource: Send + Sync + 'static {}
/// Use `Option<Res<T>>` instead if the resource might not always exist.
pub struct Res<'w, T: Resource> {
value: &'w T,
ticks: &'w ComponentTicks,
added: &'w Tick,
changed: &'w Tick,
last_change_tick: u32,
change_tick: u32,
}
@ -296,7 +297,8 @@ impl<'w, T: Resource> Res<'w, T> {
pub fn clone(this: &Self) -> Self {
Self {
value: this.value,
ticks: this.ticks,
added: this.added,
changed: this.changed,
last_change_tick: this.last_change_tick,
change_tick: this.change_tick,
}
@ -304,13 +306,14 @@ impl<'w, T: Resource> Res<'w, T> {
/// Returns `true` if the resource was added after the system last ran.
pub fn is_added(&self) -> bool {
self.ticks.is_added(self.last_change_tick, self.change_tick)
self.added
.is_older_than(self.last_change_tick, self.change_tick)
}
/// Returns `true` if the resource was added or mutably dereferenced after the system last ran.
pub fn is_changed(&self) -> bool {
self.ticks
.is_changed(self.last_change_tick, self.change_tick)
self.changed
.is_older_than(self.last_change_tick, self.change_tick)
}
pub fn into_inner(self) -> &'w T {
@ -337,7 +340,8 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Res<'w, T> {
fn from(res: ResMut<'w, T>) -> Self {
Self {
value: res.value,
ticks: res.ticks.component_ticks,
added: res.ticks.added,
changed: res.ticks.changed,
change_tick: res.ticks.change_tick,
last_change_tick: res.ticks.last_change_tick,
}
@ -417,7 +421,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState<T> {
});
Res {
value: ptr.deref(),
ticks: ticks.deref(),
added: ticks.added.deref(),
changed: ticks.changed.deref(),
last_change_tick: system_meta.last_change_tick,
change_tick,
}
@ -458,7 +463,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState<T> {
.get_resource_with_ticks(state.0.component_id)
.map(|(ptr, ticks)| Res {
value: ptr.deref(),
ticks: ticks.deref(),
added: ticks.added.deref(),
changed: ticks.changed.deref(),
last_change_tick: system_meta.last_change_tick,
change_tick,
})
@ -530,7 +536,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState<T> {
ResMut {
value: value.value,
ticks: Ticks {
component_ticks: value.ticks.component_ticks,
added: value.ticks.added,
changed: value.ticks.changed,
last_change_tick: system_meta.last_change_tick,
change_tick,
},
@ -570,7 +577,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResMutState<T> {
.map(|value| ResMut {
value: value.value,
ticks: Ticks {
component_ticks: value.ticks.component_ticks,
added: value.ticks.added,
changed: value.ticks.changed,
last_change_tick: system_meta.last_change_tick,
change_tick,
},
@ -942,7 +950,10 @@ impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
fn from(nsm: NonSendMut<'a, T>) -> Self {
Self {
value: nsm.value,
ticks: nsm.ticks.component_ticks.to_owned(),
ticks: ComponentTicks {
added: nsm.ticks.added.to_owned(),
changed: nsm.ticks.changed.to_owned(),
},
change_tick: nsm.ticks.change_tick,
last_change_tick: nsm.ticks.last_change_tick,
}
@ -1130,11 +1141,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState<T> {
});
NonSendMut {
value: ptr.assert_unique().deref_mut(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: system_meta.last_change_tick,
change_tick,
},
ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
}
}
}
@ -1171,11 +1178,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState<T> {
.get_resource_with_ticks(state.0.component_id)
.map(|(ptr, ticks)| NonSendMut {
value: ptr.assert_unique().deref_mut(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: system_meta.last_change_tick,
change_tick,
},
ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
})
}
}

View File

@ -2,14 +2,14 @@ use crate::{
archetype::{Archetype, ArchetypeId, Archetypes},
bundle::{Bundle, BundleInfo},
change_detection::{MutUntyped, Ticks},
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
component::{Component, ComponentId, ComponentTicks, Components, StorageType, TickCells},
entity::{Entities, Entity, EntityLocation},
storage::{SparseSet, Storages},
world::{Mut, World},
};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
use bevy_ptr::{OwningPtr, Ptr};
use bevy_utils::tracing::debug;
use std::{any::TypeId, cell::UnsafeCell};
use std::any::TypeId;
/// A read-only reference to a particular [`Entity`] and all of its components
#[derive(Copy, Clone)]
@ -77,12 +77,9 @@ impl<'w> EntityRef<'w> {
/// Retrieves the change ticks for the given component. This can be useful for implementing change
/// detection in custom runtimes.
#[inline]
pub fn get_change_ticks<T: Component>(&self) -> Option<&'w ComponentTicks> {
pub fn get_change_ticks<T: Component>(&self) -> Option<ComponentTicks> {
// SAFETY: entity location is valid
unsafe {
get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|ticks| ticks.deref())
}
unsafe { get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) }
}
/// Gets a mutable reference to the component of type `T` associated with
@ -104,11 +101,7 @@ impl<'w> EntityRef<'w> {
get_component_and_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick,
change_tick,
},
ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick),
})
}
}
@ -208,12 +201,9 @@ impl<'w> EntityMut<'w> {
/// Retrieves the change ticks for the given component. This can be useful for implementing change
/// detection in custom runtimes.
#[inline]
pub fn get_change_ticks<T: Component>(&self) -> Option<&ComponentTicks> {
pub fn get_change_ticks<T: Component>(&self) -> Option<ComponentTicks> {
// SAFETY: entity location is valid
unsafe {
get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|ticks| ticks.deref())
}
unsafe { get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) }
}
/// Gets a mutable reference to the component of type `T` associated with
@ -231,11 +221,11 @@ impl<'w> EntityMut<'w> {
get_component_and_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: self.world.last_change_tick(),
change_tick: self.world.read_change_tick(),
},
ticks: Ticks::from_tick_cells(
ticks,
self.world.last_change_tick(),
self.world.read_change_tick(),
),
})
}
@ -635,7 +625,7 @@ unsafe fn get_component_and_ticks(
component_id: ComponentId,
entity: Entity,
location: EntityLocation,
) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
) -> Option<(Ptr<'_>, TickCells<'_>)> {
let archetype = &world.archetypes[location.archetype_id];
let component_info = world.components.get_info_unchecked(component_id);
match component_info.storage_type() {
@ -646,7 +636,10 @@ unsafe fn get_component_and_ticks(
// SAFETY: archetypes only store valid table_rows and the stored component type is T
Some((
components.get_data_unchecked(table_row),
components.get_ticks_unchecked(table_row),
TickCells {
added: components.get_added_ticks_unchecked(table_row),
changed: components.get_changed_ticks_unchecked(table_row),
},
))
}
StorageType::SparseSet => world
@ -663,7 +656,7 @@ unsafe fn get_ticks(
component_id: ComponentId,
entity: Entity,
location: EntityLocation,
) -> Option<&UnsafeCell<ComponentTicks>> {
) -> Option<ComponentTicks> {
let archetype = &world.archetypes[location.archetype_id];
let component_info = world.components.get_info_unchecked(component_id);
match component_info.storage_type() {
@ -747,7 +740,7 @@ pub(crate) unsafe fn get_component_and_ticks_with_type(
type_id: TypeId,
entity: Entity,
location: EntityLocation,
) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
) -> Option<(Ptr<'_>, TickCells<'_>)> {
let component_id = world.components.get_id(type_id)?;
get_component_and_ticks(world, component_id, entity, location)
}
@ -759,7 +752,7 @@ pub(crate) unsafe fn get_ticks_with_type(
type_id: TypeId,
entity: Entity,
location: EntityLocation,
) -> Option<&UnsafeCell<ComponentTicks>> {
) -> Option<ComponentTicks> {
let component_id = world.components.get_id(type_id)?;
get_ticks(world, component_id, entity, location)
}
@ -911,11 +904,7 @@ pub(crate) unsafe fn get_mut<T: Component>(
get_component_and_ticks_with_type(world, TypeId::of::<T>(), entity, location).map(
|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick,
change_tick,
},
ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick),
},
)
}
@ -932,11 +921,11 @@ pub(crate) unsafe fn get_mut_by_id(
get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| {
MutUntyped {
value: value.assert_unique(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: world.last_change_tick(),
change_tick: world.read_change_tick(),
},
ticks: Ticks::from_tick_cells(
ticks,
world.last_change_tick(),
world.read_change_tick(),
),
}
})
}

View File

@ -12,18 +12,17 @@ use crate::{
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
change_detection::{MutUntyped, Ticks},
component::{
Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components,
Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, TickCells,
},
entity::{AllocAtWithoutReplacement, Entities, Entity},
query::{QueryState, ReadOnlyWorldQuery, WorldQuery},
storage::{ResourceData, SparseSet, Storages},
system::Resource,
};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
use bevy_ptr::{OwningPtr, Ptr};
use bevy_utils::tracing::warn;
use std::{
any::TypeId,
cell::UnsafeCell,
fmt,
sync::atomic::{AtomicU32, Ordering},
};
@ -1001,7 +1000,7 @@ impl World {
pub(crate) fn get_resource_with_ticks(
&self,
component_id: ComponentId,
) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
) -> Option<(Ptr<'_>, TickCells<'_>)> {
self.storages.resources.get(component_id)?.get_with_ticks()
}
@ -1194,7 +1193,8 @@ impl World {
let value_mut = Mut {
value: &mut value,
ticks: Ticks {
component_ticks: &mut ticks,
added: &mut ticks.added,
changed: &mut ticks.changed,
last_change_tick,
change_tick,
},
@ -1273,11 +1273,7 @@ impl World {
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
Some(Mut {
value: ptr.assert_unique().deref_mut(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: self.last_change_tick(),
change_tick: self.read_change_tick(),
},
ticks: Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()),
})
}
@ -1457,14 +1453,11 @@ impl World {
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
// SAFE: This function has exclusive access to the world so nothing aliases `ticks`.
let ticks = Ticks {
// SAFETY:
// - index is in-bounds because the column is initialized and non-empty
// - no other reference to the ticks of the same row can exist at the same time
component_ticks: unsafe { ticks.deref_mut() },
last_change_tick: self.last_change_tick(),
change_tick: self.read_change_tick(),
// SAFETY: This function has exclusive access to the world so nothing aliases `ticks`.
// - index is in-bounds because the column is initialized and non-empty
// - no other reference to the ticks of the same row can exist at the same time
let ticks = unsafe {
Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick())
};
Some(MutUntyped {