mirror of
https://github.com/bevyengine/bevy
synced 2024-11-13 00:17:27 +00:00
Track source location in change detection (#14034)
# Objective - Make it possible to know *what* changed your component or resource. - Common need when debugging, when you want to know the last code location that mutated a value in the ECS. - This feature would be very useful for the editor alongside system stepping. ## Solution - Adds the caller location to column data. - Mutations now `track_caller` all the way up to the public API. - Commands that invoke these functions immediately call `Location::caller`, and pass this into the functions, instead of the functions themselves attempting to get the caller. This would not work for commands which are deferred, as the commands are executed by the scheduler, not the user's code. ## Testing - The `component_change_detection` example now shows where the component was mutated: ``` 2024-07-28T06:57:48.946022Z INFO component_change_detection: Entity { index: 1, generation: 1 }: New value: MyComponent(0.0) 2024-07-28T06:57:49.004371Z INFO component_change_detection: Entity { index: 1, generation: 1 }: New value: MyComponent(1.0) 2024-07-28T06:57:49.012738Z WARN component_change_detection: Change detected! -> value: Ref(MyComponent(1.0)) -> added: false -> changed: true -> changed by: examples/ecs/component_change_detection.rs:36:23 ``` - It's also possible to inspect change location from a debugger: <img width="608" alt="image" src="https://github.com/user-attachments/assets/c90ecc7a-0462-457a-80ae-42e7f5d346b4"> --- ## Changelog - Added source locations to ECS change detection behind the `track_change_detection` flag. ## Migration Guide - Added `changed_by` field to many internal ECS functions used with change detection when the `track_change_detection` feature flag is enabled. Use Location::caller() to provide the source of the function call. --------- Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
parent
adb4709d08
commit
9575b20d31
21 changed files with 957 additions and 164 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -348,6 +348,9 @@ ios_simulator = ["bevy_internal/ios_simulator"]
|
||||||
# Enable built in global state machines
|
# Enable built in global state machines
|
||||||
bevy_state = ["bevy_internal/bevy_state"]
|
bevy_state = ["bevy_internal/bevy_state"]
|
||||||
|
|
||||||
|
# Enables source location tracking for change detection, which can assist with debugging
|
||||||
|
track_change_detection = ["bevy_internal/track_change_detection"]
|
||||||
|
|
||||||
# Enable function reflection
|
# Enable function reflection
|
||||||
reflect_functions = ["bevy_internal/reflect_functions"]
|
reflect_functions = ["bevy_internal/reflect_functions"]
|
||||||
|
|
||||||
|
@ -1611,13 +1614,14 @@ category = "ECS (Entity Component System)"
|
||||||
wasm = false
|
wasm = false
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "component_change_detection"
|
name = "change_detection"
|
||||||
path = "examples/ecs/component_change_detection.rs"
|
path = "examples/ecs/change_detection.rs"
|
||||||
doc-scrape-examples = true
|
doc-scrape-examples = true
|
||||||
|
required-features = ["track_change_detection"]
|
||||||
|
|
||||||
[package.metadata.example.component_change_detection]
|
[package.metadata.example.change_detection]
|
||||||
name = "Component Change Detection"
|
name = "Change Detection"
|
||||||
description = "Change detection on components"
|
description = "Change detection on components and resources"
|
||||||
category = "ECS (Entity Component System)"
|
category = "ECS (Entity Component System)"
|
||||||
wasm = false
|
wasm = false
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,12 @@ categories = ["game-engines", "data-structures"]
|
||||||
rust-version = "1.77.0"
|
rust-version = "1.77.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["bevy_reflect"]
|
||||||
trace = []
|
trace = []
|
||||||
multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
|
multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
|
||||||
bevy_debug_stepping = []
|
bevy_debug_stepping = []
|
||||||
default = ["bevy_reflect"]
|
|
||||||
serialize = ["dep:serde"]
|
serialize = ["dep:serde"]
|
||||||
|
track_change_detection = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" }
|
bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" }
|
||||||
|
|
|
@ -22,6 +22,8 @@ use crate::{
|
||||||
|
|
||||||
use bevy_ptr::{ConstNonNull, OwningPtr};
|
use bevy_ptr::{ConstNonNull, OwningPtr};
|
||||||
use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap};
|
use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
|
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
|
||||||
|
@ -401,6 +403,7 @@ impl BundleInfo {
|
||||||
table_row: TableRow,
|
table_row: TableRow,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
bundle: T,
|
bundle: T,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
) {
|
) {
|
||||||
// NOTE: get_components calls this closure on each component in "bundle order".
|
// NOTE: get_components calls this closure on each component in "bundle order".
|
||||||
// bundle_info.component_ids are also in "bundle order"
|
// bundle_info.component_ids are also in "bundle order"
|
||||||
|
@ -417,10 +420,22 @@ impl BundleInfo {
|
||||||
let status = unsafe { bundle_component_status.get_status(bundle_component) };
|
let status = unsafe { bundle_component_status.get_status(bundle_component) };
|
||||||
match status {
|
match status {
|
||||||
ComponentStatus::Added => {
|
ComponentStatus::Added => {
|
||||||
column.initialize(table_row, component_ptr, change_tick);
|
column.initialize(
|
||||||
|
table_row,
|
||||||
|
component_ptr,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ComponentStatus::Mutated => {
|
ComponentStatus::Mutated => {
|
||||||
column.replace(table_row, component_ptr, change_tick);
|
column.replace(
|
||||||
|
table_row,
|
||||||
|
component_ptr,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,7 +444,13 @@ impl BundleInfo {
|
||||||
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
||||||
// a sparse set exists for the component.
|
// a sparse set exists for the component.
|
||||||
unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
|
unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
|
||||||
sparse_set.insert(entity, component_ptr, change_tick);
|
sparse_set.insert(
|
||||||
|
entity,
|
||||||
|
component_ptr,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bundle_component += 1;
|
bundle_component += 1;
|
||||||
|
@ -664,6 +685,7 @@ impl<'w> BundleInserter<'w> {
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
location: EntityLocation,
|
location: EntityLocation,
|
||||||
bundle: T,
|
bundle: T,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location<'static>,
|
||||||
) -> EntityLocation {
|
) -> EntityLocation {
|
||||||
let bundle_info = self.bundle_info.as_ref();
|
let bundle_info = self.bundle_info.as_ref();
|
||||||
let add_bundle = self.add_bundle.as_ref();
|
let add_bundle = self.add_bundle.as_ref();
|
||||||
|
@ -706,6 +728,8 @@ impl<'w> BundleInserter<'w> {
|
||||||
location.table_row,
|
location.table_row,
|
||||||
self.change_tick,
|
self.change_tick,
|
||||||
bundle,
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
);
|
);
|
||||||
|
|
||||||
(archetype, location)
|
(archetype, location)
|
||||||
|
@ -744,6 +768,8 @@ impl<'w> BundleInserter<'w> {
|
||||||
result.table_row,
|
result.table_row,
|
||||||
self.change_tick,
|
self.change_tick,
|
||||||
bundle,
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
);
|
);
|
||||||
|
|
||||||
(new_archetype, new_location)
|
(new_archetype, new_location)
|
||||||
|
@ -823,6 +849,8 @@ impl<'w> BundleInserter<'w> {
|
||||||
move_result.new_row,
|
move_result.new_row,
|
||||||
self.change_tick,
|
self.change_tick,
|
||||||
bundle,
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
);
|
);
|
||||||
|
|
||||||
(new_archetype, new_location)
|
(new_archetype, new_location)
|
||||||
|
@ -919,6 +947,7 @@ impl<'w> BundleSpawner<'w> {
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
bundle: T,
|
bundle: T,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
) -> EntityLocation {
|
) -> EntityLocation {
|
||||||
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
|
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
|
||||||
let bundle_info = self.bundle_info.as_ref();
|
let bundle_info = self.bundle_info.as_ref();
|
||||||
|
@ -941,6 +970,8 @@ impl<'w> BundleSpawner<'w> {
|
||||||
table_row,
|
table_row,
|
||||||
self.change_tick,
|
self.change_tick,
|
||||||
bundle,
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
);
|
);
|
||||||
entities.set(entity.index(), location);
|
entities.set(entity.index(), location);
|
||||||
location
|
location
|
||||||
|
@ -969,11 +1000,20 @@ impl<'w> BundleSpawner<'w> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `T` must match this [`BundleInfo`]'s type
|
/// `T` must match this [`BundleInfo`]'s type
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn spawn<T: Bundle>(&mut self, bundle: T) -> Entity {
|
pub unsafe fn spawn<T: Bundle>(
|
||||||
|
&mut self,
|
||||||
|
bundle: T,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
|
) -> Entity {
|
||||||
let entity = self.entities().alloc();
|
let entity = self.entities().alloc();
|
||||||
// SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type
|
// SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type
|
||||||
unsafe {
|
unsafe {
|
||||||
self.spawn_non_existent(entity, bundle);
|
self.spawn_non_existent(
|
||||||
|
entity,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
entity
|
entity
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,13 @@ use crate::{
|
||||||
ptr::PtrMut,
|
ptr::PtrMut,
|
||||||
system::Resource,
|
system::Resource,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use bevy_ptr::ThinSlicePtr;
|
||||||
use bevy_ptr::{Ptr, UnsafeCellDeref};
|
use bevy_ptr::{Ptr, UnsafeCellDeref};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::{cell::UnsafeCell, panic::Location};
|
||||||
|
|
||||||
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
|
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
|
||||||
///
|
///
|
||||||
|
@ -63,6 +67,10 @@ pub trait DetectChanges {
|
||||||
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
|
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
|
||||||
/// [`SystemParam`](crate::system::SystemParam).
|
/// [`SystemParam`](crate::system::SystemParam).
|
||||||
fn last_changed(&self) -> Tick;
|
fn last_changed(&self) -> Tick;
|
||||||
|
|
||||||
|
/// The location that last caused this to change.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
fn changed_by(&self) -> &'static Location<'static>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Types that implement reliable change detection.
|
/// Types that implement reliable change detection.
|
||||||
|
@ -167,6 +175,7 @@ pub trait DetectChangesMut: DetectChanges {
|
||||||
/// # assert!(!score_changed.run((), &mut world));
|
/// # assert!(!score_changed.run((), &mut world));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn set_if_neq(&mut self, value: Self::Inner) -> bool
|
fn set_if_neq(&mut self, value: Self::Inner) -> bool
|
||||||
where
|
where
|
||||||
Self::Inner: Sized + PartialEq,
|
Self::Inner: Sized + PartialEq,
|
||||||
|
@ -280,6 +289,12 @@ macro_rules! change_detection_impl {
|
||||||
fn last_changed(&self) -> Tick {
|
fn last_changed(&self) -> Tick {
|
||||||
*self.ticks.changed
|
*self.ticks.changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
fn changed_by(&self) -> &'static Location<'static> {
|
||||||
|
self.changed_by
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> {
|
impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> {
|
||||||
|
@ -306,13 +321,23 @@ macro_rules! change_detection_mut_impl {
|
||||||
type Inner = $target;
|
type Inner = $target;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn set_changed(&mut self) {
|
fn set_changed(&mut self) {
|
||||||
*self.ticks.changed = self.ticks.this_run;
|
*self.ticks.changed = self.ticks.this_run;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by = Location::caller();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn set_last_changed(&mut self, last_changed: Tick) {
|
fn set_last_changed(&mut self, last_changed: Tick) {
|
||||||
*self.ticks.changed = last_changed;
|
*self.ticks.changed = last_changed;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by = Location::caller();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -323,8 +348,13 @@ macro_rules! change_detection_mut_impl {
|
||||||
|
|
||||||
impl<$($generics),* : ?Sized $(+ $traits)?> DerefMut for $name<$($generics),*> {
|
impl<$($generics),* : ?Sized $(+ $traits)?> DerefMut for $name<$($generics),*> {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
self.set_changed();
|
self.set_changed();
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by = Location::caller();
|
||||||
|
}
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,7 +391,9 @@ macro_rules! impl_methods {
|
||||||
changed: self.ticks.changed,
|
changed: self.ticks.changed,
|
||||||
last_run: self.ticks.last_run,
|
last_run: self.ticks.last_run,
|
||||||
this_run: self.ticks.this_run,
|
this_run: self.ticks.this_run,
|
||||||
}
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,6 +423,8 @@ macro_rules! impl_methods {
|
||||||
Mut {
|
Mut {
|
||||||
value: f(self.value),
|
value: f(self.value),
|
||||||
ticks: self.ticks,
|
ticks: self.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,6 +535,8 @@ impl<'w> From<TicksMut<'w>> for Ticks<'w> {
|
||||||
pub struct Res<'w, T: ?Sized + Resource> {
|
pub struct Res<'w, T: ?Sized + Resource> {
|
||||||
pub(crate) value: &'w T,
|
pub(crate) value: &'w T,
|
||||||
pub(crate) ticks: Ticks<'w>,
|
pub(crate) ticks: Ticks<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, T: Resource> Res<'w, T> {
|
impl<'w, T: Resource> Res<'w, T> {
|
||||||
|
@ -513,6 +549,8 @@ impl<'w, T: Resource> Res<'w, T> {
|
||||||
Self {
|
Self {
|
||||||
value: this.value,
|
value: this.value,
|
||||||
ticks: this.ticks.clone(),
|
ticks: this.ticks.clone(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: this.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +567,8 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Res<'w, T> {
|
||||||
Self {
|
Self {
|
||||||
value: res.value,
|
value: res.value,
|
||||||
ticks: res.ticks.into(),
|
ticks: res.ticks.into(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: res.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -561,6 +601,8 @@ impl_debug!(Res<'w, T>, Resource);
|
||||||
pub struct ResMut<'w, T: ?Sized + Resource> {
|
pub struct ResMut<'w, T: ?Sized + Resource> {
|
||||||
pub(crate) value: &'w mut T,
|
pub(crate) value: &'w mut T,
|
||||||
pub(crate) ticks: TicksMut<'w>,
|
pub(crate) ticks: TicksMut<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'w mut &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 'a, T: Resource> IntoIterator for &'a ResMut<'w, T>
|
impl<'w, 'a, T: Resource> IntoIterator for &'a ResMut<'w, T>
|
||||||
|
@ -600,6 +642,8 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
|
||||||
Mut {
|
Mut {
|
||||||
value: other.value,
|
value: other.value,
|
||||||
ticks: other.ticks,
|
ticks: other.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: other.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -619,6 +663,8 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
|
||||||
pub struct NonSendMut<'w, T: ?Sized + 'static> {
|
pub struct NonSendMut<'w, T: ?Sized + 'static> {
|
||||||
pub(crate) value: &'w mut T,
|
pub(crate) value: &'w mut T,
|
||||||
pub(crate) ticks: TicksMut<'w>,
|
pub(crate) ticks: TicksMut<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'w mut &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
change_detection_impl!(NonSendMut<'w, T>, T,);
|
change_detection_impl!(NonSendMut<'w, T>, T,);
|
||||||
|
@ -633,6 +679,8 @@ impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
|
||||||
Mut {
|
Mut {
|
||||||
value: other.value,
|
value: other.value,
|
||||||
ticks: other.ticks,
|
ticks: other.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: other.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -664,6 +712,8 @@ impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
|
||||||
pub struct Ref<'w, T: ?Sized> {
|
pub struct Ref<'w, T: ?Sized> {
|
||||||
pub(crate) value: &'w T,
|
pub(crate) value: &'w T,
|
||||||
pub(crate) ticks: Ticks<'w>,
|
pub(crate) ticks: Ticks<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, T: ?Sized> Ref<'w, T> {
|
impl<'w, T: ?Sized> Ref<'w, T> {
|
||||||
|
@ -680,6 +730,8 @@ impl<'w, T: ?Sized> Ref<'w, T> {
|
||||||
Ref {
|
Ref {
|
||||||
value: f(self.value),
|
value: f(self.value),
|
||||||
ticks: self.ticks,
|
ticks: self.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,6 +752,7 @@ impl<'w, T: ?Sized> Ref<'w, T> {
|
||||||
changed: &'w Tick,
|
changed: &'w Tick,
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
) -> Ref<'w, T> {
|
) -> Ref<'w, T> {
|
||||||
Ref {
|
Ref {
|
||||||
value,
|
value,
|
||||||
|
@ -709,6 +762,8 @@ impl<'w, T: ?Sized> Ref<'w, T> {
|
||||||
last_run,
|
last_run,
|
||||||
this_run,
|
this_run,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: caller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -790,6 +845,8 @@ impl_debug!(Ref<'w, T>,);
|
||||||
pub struct Mut<'w, T: ?Sized> {
|
pub struct Mut<'w, T: ?Sized> {
|
||||||
pub(crate) value: &'w mut T,
|
pub(crate) value: &'w mut T,
|
||||||
pub(crate) ticks: TicksMut<'w>,
|
pub(crate) ticks: TicksMut<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'w mut &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, T: ?Sized> Mut<'w, T> {
|
impl<'w, T: ?Sized> Mut<'w, T> {
|
||||||
|
@ -814,6 +871,7 @@ impl<'w, T: ?Sized> Mut<'w, T> {
|
||||||
last_changed: &'w mut Tick,
|
last_changed: &'w mut Tick,
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'w mut &'static Location<'static>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
|
@ -823,6 +881,8 @@ impl<'w, T: ?Sized> Mut<'w, T> {
|
||||||
last_run,
|
last_run,
|
||||||
this_run,
|
this_run,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: caller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -832,6 +892,8 @@ impl<'w, T: ?Sized> From<Mut<'w, T>> for Ref<'w, T> {
|
||||||
Self {
|
Self {
|
||||||
value: mut_ref.value,
|
value: mut_ref.value,
|
||||||
ticks: mut_ref.ticks.into(),
|
ticks: mut_ref.ticks.into(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: mut_ref.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -877,6 +939,8 @@ impl_debug!(Mut<'w, T>,);
|
||||||
pub struct MutUntyped<'w> {
|
pub struct MutUntyped<'w> {
|
||||||
pub(crate) value: PtrMut<'w>,
|
pub(crate) value: PtrMut<'w>,
|
||||||
pub(crate) ticks: TicksMut<'w>,
|
pub(crate) ticks: TicksMut<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) changed_by: &'w mut &'static core::panic::Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> MutUntyped<'w> {
|
impl<'w> MutUntyped<'w> {
|
||||||
|
@ -901,6 +965,8 @@ impl<'w> MutUntyped<'w> {
|
||||||
last_run: self.ticks.last_run,
|
last_run: self.ticks.last_run,
|
||||||
this_run: self.ticks.this_run,
|
this_run: self.ticks.this_run,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -951,6 +1017,8 @@ impl<'w> MutUntyped<'w> {
|
||||||
Mut {
|
Mut {
|
||||||
value: f(self.value),
|
value: f(self.value),
|
||||||
ticks: self.ticks,
|
ticks: self.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,6 +1031,9 @@ impl<'w> MutUntyped<'w> {
|
||||||
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
|
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
|
||||||
value: unsafe { self.value.deref_mut() },
|
value: unsafe { self.value.deref_mut() },
|
||||||
ticks: self.ticks,
|
ticks: self.ticks,
|
||||||
|
// SAFETY: `caller` is `Aligned`.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: self.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -986,22 +1057,39 @@ impl<'w> DetectChanges for MutUntyped<'w> {
|
||||||
fn last_changed(&self) -> Tick {
|
fn last_changed(&self) -> Tick {
|
||||||
*self.ticks.changed
|
*self.ticks.changed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
fn changed_by(&self) -> &'static Location<'static> {
|
||||||
|
self.changed_by
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> DetectChangesMut for MutUntyped<'w> {
|
impl<'w> DetectChangesMut for MutUntyped<'w> {
|
||||||
type Inner = PtrMut<'w>;
|
type Inner = PtrMut<'w>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn set_changed(&mut self) {
|
fn set_changed(&mut self) {
|
||||||
*self.ticks.changed = self.ticks.this_run;
|
*self.ticks.changed = self.ticks.this_run;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by = Location::caller();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn set_last_changed(&mut self, last_changed: Tick) {
|
fn set_last_changed(&mut self, last_changed: Tick) {
|
||||||
*self.ticks.changed = last_changed;
|
*self.ticks.changed = last_changed;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by = Location::caller();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
|
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
|
||||||
&mut self.value
|
&mut self.value
|
||||||
}
|
}
|
||||||
|
@ -1020,16 +1108,71 @@ impl<'w, T> From<Mut<'w, T>> for MutUntyped<'w> {
|
||||||
MutUntyped {
|
MutUntyped {
|
||||||
value: value.value.into(),
|
value: value.value.into(),
|
||||||
ticks: value.ticks,
|
ticks: value.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: value.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type alias to [`&'static Location<'static>`](std::panic::Location) when the `track_change_detection` feature is
|
||||||
|
/// enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// This is primarily used in places where `#[cfg(...)]` attributes are not allowed, such as
|
||||||
|
/// function return types. Because unit is a zero-sized type, it is the equivalent of not using a
|
||||||
|
/// `Location` at all.
|
||||||
|
///
|
||||||
|
/// Please use this type sparingly: prefer normal `#[cfg(...)]` attributes when possible.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) type MaybeLocation = &'static Location<'static>;
|
||||||
|
|
||||||
|
/// A type alias to [`&'static Location<'static>`](std::panic::Location) when the `track_change_detection` feature is
|
||||||
|
/// enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// This is primarily used in places where `#[cfg(...)]` attributes are not allowed, such as
|
||||||
|
/// function return types. Because unit is a zero-sized type, it is the equivalent of not using a
|
||||||
|
/// `Location` at all.
|
||||||
|
///
|
||||||
|
/// Please use this type sparingly: prefer normal `#[cfg(...)]` attributes when possible.
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
pub(crate) type MaybeLocation = ();
|
||||||
|
|
||||||
|
/// A type alias to `&UnsafeCell<&'static Location<'static>>` when the `track_change_detection`
|
||||||
|
/// feature is enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// See [`MaybeLocation`] for further information.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) type MaybeUnsafeCellLocation<'a> = &'a UnsafeCell<&'static Location<'static>>;
|
||||||
|
|
||||||
|
/// A type alias to `&UnsafeCell<&'static Location<'static>>` when the `track_change_detection`
|
||||||
|
/// feature is enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// See [`MaybeLocation`] for further information.
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
pub(crate) type MaybeUnsafeCellLocation<'a> = ();
|
||||||
|
|
||||||
|
/// A type alias to `ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>` when the
|
||||||
|
/// `track_change_detection` feature is enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// See [`MaybeLocation`] for further information.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub(crate) type MaybeThinSlicePtrLocation<'w> =
|
||||||
|
ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>;
|
||||||
|
|
||||||
|
/// A type alias to `ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>` when the
|
||||||
|
/// `track_change_detection` feature is enabled, and the unit type `()` when it is not.
|
||||||
|
///
|
||||||
|
/// See [`MaybeLocation`] for further information.
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
pub(crate) type MaybeThinSlicePtrLocation<'w> = ();
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bevy_ecs_macros::Resource;
|
use bevy_ecs_macros::Resource;
|
||||||
use bevy_ptr::PtrMut;
|
use bevy_ptr::PtrMut;
|
||||||
use bevy_reflect::{FromType, ReflectFromPtr};
|
use bevy_reflect::{FromType, ReflectFromPtr};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
|
@ -1160,9 +1303,14 @@ mod tests {
|
||||||
this_run: Tick::new(4),
|
this_run: Tick::new(4),
|
||||||
};
|
};
|
||||||
let mut res = R {};
|
let mut res = R {};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let res_mut = ResMut {
|
let res_mut = ResMut {
|
||||||
value: &mut res,
|
value: &mut res,
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut caller,
|
||||||
};
|
};
|
||||||
|
|
||||||
let into_mut: Mut<R> = res_mut.into();
|
let into_mut: Mut<R> = res_mut.into();
|
||||||
|
@ -1179,6 +1327,8 @@ mod tests {
|
||||||
changed: Tick::new(3),
|
changed: Tick::new(3),
|
||||||
};
|
};
|
||||||
let mut res = R {};
|
let mut res = R {};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let val = Mut::new(
|
let val = Mut::new(
|
||||||
&mut res,
|
&mut res,
|
||||||
|
@ -1186,6 +1336,8 @@ mod tests {
|
||||||
&mut component_ticks.changed,
|
&mut component_ticks.changed,
|
||||||
Tick::new(2), // last_run
|
Tick::new(2), // last_run
|
||||||
Tick::new(4), // this_run
|
Tick::new(4), // this_run
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
&mut caller,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(!val.is_added());
|
assert!(!val.is_added());
|
||||||
|
@ -1205,9 +1357,14 @@ mod tests {
|
||||||
this_run: Tick::new(4),
|
this_run: Tick::new(4),
|
||||||
};
|
};
|
||||||
let mut res = R {};
|
let mut res = R {};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let non_send_mut = NonSendMut {
|
let non_send_mut = NonSendMut {
|
||||||
value: &mut res,
|
value: &mut res,
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut caller,
|
||||||
};
|
};
|
||||||
|
|
||||||
let into_mut: Mut<R> = non_send_mut.into();
|
let into_mut: Mut<R> = non_send_mut.into();
|
||||||
|
@ -1236,9 +1393,14 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut outer = Outer(0);
|
let mut outer = Outer(0);
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let ptr = Mut {
|
let ptr = Mut {
|
||||||
value: &mut outer,
|
value: &mut outer,
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut caller,
|
||||||
};
|
};
|
||||||
assert!(!ptr.is_changed());
|
assert!(!ptr.is_changed());
|
||||||
|
|
||||||
|
@ -1321,9 +1483,14 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut value: i32 = 5;
|
let mut value: i32 = 5;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let value = MutUntyped {
|
let value = MutUntyped {
|
||||||
value: PtrMut::from(&mut value),
|
value: PtrMut::from(&mut value),
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut caller,
|
||||||
};
|
};
|
||||||
|
|
||||||
let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
|
let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
|
||||||
|
@ -1354,9 +1521,14 @@ mod tests {
|
||||||
this_run: Tick::new(4),
|
this_run: Tick::new(4),
|
||||||
};
|
};
|
||||||
let mut c = C {};
|
let mut c = C {};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let mut caller = Location::caller();
|
||||||
|
|
||||||
let mut_typed = Mut {
|
let mut_typed = Mut {
|
||||||
value: &mut c,
|
value: &mut c,
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut caller,
|
||||||
};
|
};
|
||||||
|
|
||||||
let into_mut: MutUntyped = mut_typed.into();
|
let into_mut: MutUntyped = mut_typed.into();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::{Archetype, Archetypes},
|
archetype::{Archetype, Archetypes},
|
||||||
change_detection::{Ticks, TicksMut},
|
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
|
||||||
component::{Component, ComponentId, Components, StorageType, Tick},
|
component::{Component, ComponentId, Components, StorageType, Tick},
|
||||||
entity::{Entities, Entity, EntityLocation},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||||
|
@ -1026,6 +1026,7 @@ pub struct RefFetch<'w, T> {
|
||||||
ThinSlicePtr<'w, UnsafeCell<T>>,
|
ThinSlicePtr<'w, UnsafeCell<T>>,
|
||||||
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
||||||
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
||||||
|
MaybeThinSlicePtrLocation<'w>,
|
||||||
)>,
|
)>,
|
||||||
// T::STORAGE_TYPE = StorageType::SparseSet
|
// T::STORAGE_TYPE = StorageType::SparseSet
|
||||||
sparse_set: Option<&'w ComponentSparseSet>,
|
sparse_set: Option<&'w ComponentSparseSet>,
|
||||||
|
@ -1115,6 +1116,10 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
column.get_data_slice().into(),
|
column.get_data_slice().into(),
|
||||||
column.get_added_ticks_slice().into(),
|
column.get_added_ticks_slice().into(),
|
||||||
column.get_changed_ticks_slice().into(),
|
column.get_changed_ticks_slice().into(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
column.get_changed_by_slice().into(),
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,7 +1132,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
match T::STORAGE_TYPE {
|
match T::STORAGE_TYPE {
|
||||||
StorageType::Table => {
|
StorageType::Table => {
|
||||||
// SAFETY: STORAGE_TYPE = Table
|
// SAFETY: STORAGE_TYPE = Table
|
||||||
let (table_components, added_ticks, changed_ticks) =
|
let (table_components, added_ticks, changed_ticks, _callers) =
|
||||||
unsafe { fetch.table_data.debug_checked_unwrap() };
|
unsafe { fetch.table_data.debug_checked_unwrap() };
|
||||||
|
|
||||||
// SAFETY: The caller ensures `table_row` is in range.
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
|
@ -1136,6 +1141,9 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
let added = unsafe { added_ticks.get(table_row.as_usize()) };
|
let added = unsafe { added_ticks.get(table_row.as_usize()) };
|
||||||
// SAFETY: The caller ensures `table_row` is in range.
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
let changed = unsafe { changed_ticks.get(table_row.as_usize()) };
|
let changed = unsafe { changed_ticks.get(table_row.as_usize()) };
|
||||||
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = unsafe { _callers.get(table_row.as_usize()) };
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
value: component.deref(),
|
value: component.deref(),
|
||||||
|
@ -1145,6 +1153,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
this_run: fetch.this_run,
|
this_run: fetch.this_run,
|
||||||
last_run: fetch.last_run,
|
last_run: fetch.last_run,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: caller.deref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StorageType::SparseSet => {
|
StorageType::SparseSet => {
|
||||||
|
@ -1152,7 +1162,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
|
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
|
||||||
|
|
||||||
// SAFETY: The caller ensures `entity` is in range.
|
// SAFETY: The caller ensures `entity` is in range.
|
||||||
let (component, ticks) = unsafe {
|
let (component, ticks, _caller) = unsafe {
|
||||||
component_sparse_set
|
component_sparse_set
|
||||||
.get_with_ticks(entity)
|
.get_with_ticks(entity)
|
||||||
.debug_checked_unwrap()
|
.debug_checked_unwrap()
|
||||||
|
@ -1161,6 +1171,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||||
Ref {
|
Ref {
|
||||||
value: component.deref(),
|
value: component.deref(),
|
||||||
ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1209,6 +1221,7 @@ pub struct WriteFetch<'w, T> {
|
||||||
ThinSlicePtr<'w, UnsafeCell<T>>,
|
ThinSlicePtr<'w, UnsafeCell<T>>,
|
||||||
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
||||||
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
ThinSlicePtr<'w, UnsafeCell<Tick>>,
|
||||||
|
MaybeThinSlicePtrLocation<'w>,
|
||||||
)>,
|
)>,
|
||||||
// T::STORAGE_TYPE = StorageType::SparseSet
|
// T::STORAGE_TYPE = StorageType::SparseSet
|
||||||
sparse_set: Option<&'w ComponentSparseSet>,
|
sparse_set: Option<&'w ComponentSparseSet>,
|
||||||
|
@ -1298,6 +1311,10 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
column.get_data_slice().into(),
|
column.get_data_slice().into(),
|
||||||
column.get_added_ticks_slice().into(),
|
column.get_added_ticks_slice().into(),
|
||||||
column.get_changed_ticks_slice().into(),
|
column.get_changed_ticks_slice().into(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
column.get_changed_by_slice().into(),
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1310,7 +1327,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
match T::STORAGE_TYPE {
|
match T::STORAGE_TYPE {
|
||||||
StorageType::Table => {
|
StorageType::Table => {
|
||||||
// SAFETY: STORAGE_TYPE = Table
|
// SAFETY: STORAGE_TYPE = Table
|
||||||
let (table_components, added_ticks, changed_ticks) =
|
let (table_components, added_ticks, changed_ticks, _callers) =
|
||||||
unsafe { fetch.table_data.debug_checked_unwrap() };
|
unsafe { fetch.table_data.debug_checked_unwrap() };
|
||||||
|
|
||||||
// SAFETY: The caller ensures `table_row` is in range.
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
|
@ -1319,6 +1336,9 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
let added = unsafe { added_ticks.get(table_row.as_usize()) };
|
let added = unsafe { added_ticks.get(table_row.as_usize()) };
|
||||||
// SAFETY: The caller ensures `table_row` is in range.
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
let changed = unsafe { changed_ticks.get(table_row.as_usize()) };
|
let changed = unsafe { changed_ticks.get(table_row.as_usize()) };
|
||||||
|
// SAFETY: The caller ensures `table_row` is in range.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = unsafe { _callers.get(table_row.as_usize()) };
|
||||||
|
|
||||||
Mut {
|
Mut {
|
||||||
value: component.deref_mut(),
|
value: component.deref_mut(),
|
||||||
|
@ -1328,6 +1348,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
this_run: fetch.this_run,
|
this_run: fetch.this_run,
|
||||||
last_run: fetch.last_run,
|
last_run: fetch.last_run,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: caller.deref_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StorageType::SparseSet => {
|
StorageType::SparseSet => {
|
||||||
|
@ -1335,7 +1357,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
|
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
|
||||||
|
|
||||||
// SAFETY: The caller ensures `entity` is in range.
|
// SAFETY: The caller ensures `entity` is in range.
|
||||||
let (component, ticks) = unsafe {
|
let (component, ticks, _caller) = unsafe {
|
||||||
component_sparse_set
|
component_sparse_set
|
||||||
.get_with_ticks(entity)
|
.get_with_ticks(entity)
|
||||||
.debug_checked_unwrap()
|
.debug_checked_unwrap()
|
||||||
|
@ -1344,6 +1366,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||||
Mut {
|
Mut {
|
||||||
value: component.assert_unique().deref_mut(),
|
value: component.assert_unique().deref_mut(),
|
||||||
ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,6 +296,8 @@ impl<C: Component + Reflect> FromType<C> for ReflectComponent {
|
||||||
entity.into_mut::<C>().map(|c| Mut {
|
entity.into_mut::<C>().map(|c| Mut {
|
||||||
value: c.value as &mut dyn Reflect,
|
value: c.value as &mut dyn Reflect,
|
||||||
ticks: c.ticks,
|
ticks: c.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: c.changed_by,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
reflect_unchecked_mut: |entity| {
|
reflect_unchecked_mut: |entity| {
|
||||||
|
@ -305,6 +307,8 @@ impl<C: Component + Reflect> FromType<C> for ReflectComponent {
|
||||||
entity.get_mut::<C>().map(|c| Mut {
|
entity.get_mut::<C>().map(|c| Mut {
|
||||||
value: c.value as &mut dyn Reflect,
|
value: c.value as &mut dyn Reflect,
|
||||||
ticks: c.ticks,
|
ticks: c.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: c.changed_by,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -207,6 +207,8 @@ impl<R: Resource + FromReflect> FromType<R> for ReflectResource {
|
||||||
world.get_resource_mut::<R>().map(|res| Mut {
|
world.get_resource_mut::<R>().map(|res| Mut {
|
||||||
value: res.value as &mut dyn Reflect,
|
value: res.value as &mut dyn Reflect,
|
||||||
ticks: res.ticks,
|
ticks: res.ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: res.changed_by,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::archetype::ArchetypeComponentId;
|
use crate::archetype::ArchetypeComponentId;
|
||||||
use crate::change_detection::{MutUntyped, TicksMut};
|
use crate::change_detection::{MaybeLocation, MaybeUnsafeCellLocation, MutUntyped, TicksMut};
|
||||||
use crate::component::{ComponentId, ComponentTicks, Components, Tick, TickCells};
|
use crate::component::{ComponentId, ComponentTicks, Components, Tick, TickCells};
|
||||||
use crate::storage::{blob_vec::BlobVec, SparseSet};
|
use crate::storage::{blob_vec::BlobVec, SparseSet};
|
||||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
use std::{cell::UnsafeCell, mem::ManuallyDrop, thread::ThreadId};
|
use std::{cell::UnsafeCell, mem::ManuallyDrop, thread::ThreadId};
|
||||||
|
|
||||||
/// The type-erased backing storage and metadata for a single resource within a [`World`].
|
/// The type-erased backing storage and metadata for a single resource within a [`World`].
|
||||||
|
@ -17,6 +19,8 @@ pub struct ResourceData<const SEND: bool> {
|
||||||
type_name: String,
|
type_name: String,
|
||||||
id: ArchetypeComponentId,
|
id: ArchetypeComponentId,
|
||||||
origin_thread_id: Option<ThreadId>,
|
origin_thread_id: Option<ThreadId>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: UnsafeCell<&'static Location<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const SEND: bool> Drop for ResourceData<SEND> {
|
impl<const SEND: bool> Drop for ResourceData<SEND> {
|
||||||
|
@ -109,7 +113,9 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||||
/// original thread it was inserted in.
|
/// original thread it was inserted in.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
pub(crate) fn get_with_ticks(
|
||||||
|
&self,
|
||||||
|
) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> {
|
||||||
self.is_present().then(|| {
|
self.is_present().then(|| {
|
||||||
self.validate_access();
|
self.validate_access();
|
||||||
(
|
(
|
||||||
|
@ -119,6 +125,10 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
added: &self.added_ticks,
|
added: &self.added_ticks,
|
||||||
changed: &self.changed_ticks,
|
changed: &self.changed_ticks,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
&self.changed_by,
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -129,12 +139,15 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||||
/// original thread it was inserted in.
|
/// original thread it was inserted in.
|
||||||
pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {
|
pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {
|
||||||
let (ptr, ticks) = self.get_with_ticks()?;
|
let (ptr, ticks, _caller) = self.get_with_ticks()?;
|
||||||
Some(MutUntyped {
|
Some(MutUntyped {
|
||||||
// SAFETY: We have exclusive access to the underlying storage.
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
value: unsafe { ptr.assert_unique() },
|
value: unsafe { ptr.assert_unique() },
|
||||||
// SAFETY: We have exclusive access to the underlying storage.
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) },
|
ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) },
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
|
changed_by: unsafe { _caller.deref_mut() },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +161,12 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// - `value` must be valid for the underlying type for the resource.
|
/// - `value` must be valid for the underlying type for the resource.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: Tick) {
|
pub(crate) unsafe fn insert(
|
||||||
|
&mut self,
|
||||||
|
value: OwningPtr<'_>,
|
||||||
|
change_tick: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location,
|
||||||
|
) {
|
||||||
if self.is_present() {
|
if self.is_present() {
|
||||||
self.validate_access();
|
self.validate_access();
|
||||||
// SAFETY: The caller ensures that the provided value is valid for the underlying type and
|
// SAFETY: The caller ensures that the provided value is valid for the underlying type and
|
||||||
|
@ -165,6 +183,10 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
*self.added_ticks.deref_mut() = change_tick;
|
*self.added_ticks.deref_mut() = change_tick;
|
||||||
}
|
}
|
||||||
*self.changed_ticks.deref_mut() = change_tick;
|
*self.changed_ticks.deref_mut() = change_tick;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by.deref_mut() = caller;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a value into the resource with a pre-existing change tick. If a
|
/// Inserts a value into the resource with a pre-existing change tick. If a
|
||||||
|
@ -181,6 +203,7 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
&mut self,
|
&mut self,
|
||||||
value: OwningPtr<'_>,
|
value: OwningPtr<'_>,
|
||||||
change_ticks: ComponentTicks,
|
change_ticks: ComponentTicks,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location,
|
||||||
) {
|
) {
|
||||||
if self.is_present() {
|
if self.is_present() {
|
||||||
self.validate_access();
|
self.validate_access();
|
||||||
|
@ -198,6 +221,10 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
}
|
}
|
||||||
*self.added_ticks.deref_mut() = change_ticks.added;
|
*self.added_ticks.deref_mut() = change_ticks.added;
|
||||||
*self.changed_ticks.deref_mut() = change_ticks.changed;
|
*self.changed_ticks.deref_mut() = change_ticks.changed;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by.deref_mut() = caller;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a value from the resource, if present.
|
/// Removes a value from the resource, if present.
|
||||||
|
@ -207,7 +234,7 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
/// original thread it was inserted from.
|
/// original thread it was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use = "The returned pointer to the removed component should be used or dropped"]
|
#[must_use = "The returned pointer to the removed component should be used or dropped"]
|
||||||
pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> {
|
||||||
if !self.is_present() {
|
if !self.is_present() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -216,6 +243,13 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
}
|
}
|
||||||
// SAFETY: We've already validated that the row is present.
|
// SAFETY: We've already validated that the row is present.
|
||||||
let res = unsafe { self.data.swap_remove_and_forget_unchecked(Self::ROW) };
|
let res = unsafe { self.data.swap_remove_and_forget_unchecked(Self::ROW) };
|
||||||
|
|
||||||
|
// SAFETY: This function is being called through an exclusive mutable reference to Self
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = unsafe { *self.changed_by.deref_mut() };
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
let caller = ();
|
||||||
|
|
||||||
// SAFETY: This function is being called through an exclusive mutable reference to Self, which
|
// SAFETY: This function is being called through an exclusive mutable reference to Self, which
|
||||||
// makes it sound to read these ticks.
|
// makes it sound to read these ticks.
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -225,6 +259,7 @@ impl<const SEND: bool> ResourceData<SEND> {
|
||||||
added: self.added_ticks.read(),
|
added: self.added_ticks.read(),
|
||||||
changed: self.changed_ticks.read(),
|
changed: self.changed_ticks.read(),
|
||||||
},
|
},
|
||||||
|
caller,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,6 +368,8 @@ impl<const SEND: bool> Resources<SEND> {
|
||||||
type_name: String::from(component_info.name()),
|
type_name: String::from(component_info.name()),
|
||||||
id: f(),
|
id: f(),
|
||||||
origin_thread_id: None,
|
origin_thread_id: None,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: UnsafeCell::new(Location::caller())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
change_detection::MaybeUnsafeCellLocation,
|
||||||
component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells},
|
component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
storage::{Column, TableRow},
|
storage::{Column, TableRow},
|
||||||
};
|
};
|
||||||
use bevy_ptr::{OwningPtr, Ptr};
|
use bevy_ptr::{OwningPtr, Ptr};
|
||||||
use nonmax::NonMaxUsize;
|
use nonmax::NonMaxUsize;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData};
|
use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData};
|
||||||
|
|
||||||
type EntityIndex = u32;
|
type EntityIndex = u32;
|
||||||
|
@ -169,14 +172,26 @@ impl ComponentSparseSet {
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
value: OwningPtr<'_>,
|
value: OwningPtr<'_>,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
) {
|
) {
|
||||||
if let Some(&dense_index) = self.sparse.get(entity.index()) {
|
if let Some(&dense_index) = self.sparse.get(entity.index()) {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
assert_eq!(entity, self.entities[dense_index.as_usize()]);
|
assert_eq!(entity, self.entities[dense_index.as_usize()]);
|
||||||
self.dense.replace(dense_index, value, change_tick);
|
self.dense.replace(
|
||||||
|
dense_index,
|
||||||
|
value,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let dense_index = self.dense.len();
|
let dense_index = self.dense.len();
|
||||||
self.dense.push(value, ComponentTicks::new(change_tick));
|
self.dense.push(
|
||||||
|
value,
|
||||||
|
ComponentTicks::new(change_tick),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
self.sparse
|
self.sparse
|
||||||
.insert(entity.index(), TableRow::from_usize(dense_index));
|
.insert(entity.index(), TableRow::from_usize(dense_index));
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -222,7 +237,10 @@ impl ComponentSparseSet {
|
||||||
///
|
///
|
||||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
pub fn get_with_ticks(
|
||||||
|
&self,
|
||||||
|
entity: Entity,
|
||||||
|
) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> {
|
||||||
let dense_index = *self.sparse.get(entity.index())?;
|
let dense_index = *self.sparse.get(entity.index())?;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
assert_eq!(entity, self.entities[dense_index.as_usize()]);
|
assert_eq!(entity, self.entities[dense_index.as_usize()]);
|
||||||
|
@ -234,6 +252,10 @@ impl ComponentSparseSet {
|
||||||
added: self.dense.get_added_tick_unchecked(dense_index),
|
added: self.dense.get_added_tick_unchecked(dense_index),
|
||||||
changed: self.dense.get_changed_tick_unchecked(dense_index),
|
changed: self.dense.get_changed_tick_unchecked(dense_index),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.dense.get_changed_by_unchecked(dense_index),
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,6 +296,22 @@ impl ComponentSparseSet {
|
||||||
unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
|
unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the calling location that last changed the entity's component value.
|
||||||
|
///
|
||||||
|
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub fn get_changed_by(
|
||||||
|
&self,
|
||||||
|
entity: Entity,
|
||||||
|
) -> Option<&UnsafeCell<&'static core::panic::Location<'static>>> {
|
||||||
|
let dense_index = *self.sparse.get(entity.index())?;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
assert_eq!(entity, self.entities[dense_index.as_usize()]);
|
||||||
|
// SAFETY: if the sparse index points to something in the dense vec, it exists
|
||||||
|
unsafe { Some(self.dense.get_changed_by_unchecked(dense_index)) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if
|
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if
|
||||||
/// it exists).
|
/// it exists).
|
||||||
#[must_use = "The returned pointer must be used to drop the removed component."]
|
#[must_use = "The returned pointer must be used to drop the removed component."]
|
||||||
|
@ -284,7 +322,7 @@ impl ComponentSparseSet {
|
||||||
self.entities.swap_remove(dense_index.as_usize());
|
self.entities.swap_remove(dense_index.as_usize());
|
||||||
let is_last = dense_index.as_usize() == self.dense.len() - 1;
|
let is_last = dense_index.as_usize() == self.dense.len() - 1;
|
||||||
// SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
|
// SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
|
||||||
let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
|
let (value, _, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
|
||||||
if !is_last {
|
if !is_last {
|
||||||
let swapped_entity = self.entities[dense_index.as_usize()];
|
let swapped_entity = self.entities[dense_index.as_usize()];
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
change_detection::{MaybeLocation, MaybeUnsafeCellLocation},
|
||||||
component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells},
|
component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
|
@ -7,6 +8,8 @@ use crate::{
|
||||||
use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref};
|
use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref};
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use std::alloc::Layout;
|
use std::alloc::Layout;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
use std::{
|
use std::{
|
||||||
cell::UnsafeCell,
|
cell::UnsafeCell,
|
||||||
ops::{Index, IndexMut},
|
ops::{Index, IndexMut},
|
||||||
|
@ -152,6 +155,8 @@ pub struct Column {
|
||||||
data: BlobVec,
|
data: BlobVec,
|
||||||
added_ticks: Vec<UnsafeCell<Tick>>,
|
added_ticks: Vec<UnsafeCell<Tick>>,
|
||||||
changed_ticks: Vec<UnsafeCell<Tick>>,
|
changed_ticks: Vec<UnsafeCell<Tick>>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: Vec<UnsafeCell<&'static Location<'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Column {
|
impl Column {
|
||||||
|
@ -163,6 +168,8 @@ impl Column {
|
||||||
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
|
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
|
||||||
added_ticks: Vec::with_capacity(capacity),
|
added_ticks: Vec::with_capacity(capacity),
|
||||||
changed_ticks: Vec::with_capacity(capacity),
|
changed_ticks: Vec::with_capacity(capacity),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: Vec::with_capacity(capacity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +186,13 @@ impl Column {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Assumes data has already been allocated for the given row.
|
/// Assumes data has already been allocated for the given row.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn initialize(&mut self, row: TableRow, data: OwningPtr<'_>, tick: Tick) {
|
pub(crate) unsafe fn initialize(
|
||||||
|
&mut self,
|
||||||
|
row: TableRow,
|
||||||
|
data: OwningPtr<'_>,
|
||||||
|
tick: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
|
) {
|
||||||
debug_assert!(row.as_usize() < self.len());
|
debug_assert!(row.as_usize() < self.len());
|
||||||
self.data.initialize_unchecked(row.as_usize(), data);
|
self.data.initialize_unchecked(row.as_usize(), data);
|
||||||
*self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick;
|
*self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick;
|
||||||
|
@ -187,6 +200,10 @@ impl Column {
|
||||||
.changed_ticks
|
.changed_ticks
|
||||||
.get_unchecked_mut(row.as_usize())
|
.get_unchecked_mut(row.as_usize())
|
||||||
.get_mut() = tick;
|
.get_mut() = tick;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes component data to the column at given row.
|
/// Writes component data to the column at given row.
|
||||||
|
@ -195,13 +212,24 @@ impl Column {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Assumes data has already been allocated for the given row.
|
/// Assumes data has already been allocated for the given row.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: Tick) {
|
pub(crate) unsafe fn replace(
|
||||||
|
&mut self,
|
||||||
|
row: TableRow,
|
||||||
|
data: OwningPtr<'_>,
|
||||||
|
change_tick: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
|
) {
|
||||||
debug_assert!(row.as_usize() < self.len());
|
debug_assert!(row.as_usize() < self.len());
|
||||||
self.data.replace_unchecked(row.as_usize(), data);
|
self.data.replace_unchecked(row.as_usize(), data);
|
||||||
*self
|
*self
|
||||||
.changed_ticks
|
.changed_ticks
|
||||||
.get_unchecked_mut(row.as_usize())
|
.get_unchecked_mut(row.as_usize())
|
||||||
.get_mut() = change_tick;
|
.get_mut() = change_tick;
|
||||||
|
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current number of elements stored in the column.
|
/// Gets the current number of elements stored in the column.
|
||||||
|
@ -231,6 +259,8 @@ impl Column {
|
||||||
self.data.swap_remove_and_drop_unchecked(row.as_usize());
|
self.data.swap_remove_and_drop_unchecked(row.as_usize());
|
||||||
self.added_ticks.swap_remove(row.as_usize());
|
self.added_ticks.swap_remove(row.as_usize());
|
||||||
self.changed_ticks.swap_remove(row.as_usize());
|
self.changed_ticks.swap_remove(row.as_usize());
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.changed_by.swap_remove(row.as_usize());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes an element from the [`Column`] and returns it and its change detection ticks.
|
/// Removes an element from the [`Column`] and returns it and its change detection ticks.
|
||||||
|
@ -249,11 +279,15 @@ impl Column {
|
||||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||||
&mut self,
|
&mut self,
|
||||||
row: TableRow,
|
row: TableRow,
|
||||||
) -> (OwningPtr<'_>, ComponentTicks) {
|
) -> (OwningPtr<'_>, ComponentTicks, MaybeLocation) {
|
||||||
let data = self.data.swap_remove_and_forget_unchecked(row.as_usize());
|
let data = self.data.swap_remove_and_forget_unchecked(row.as_usize());
|
||||||
let added = self.added_ticks.swap_remove(row.as_usize()).into_inner();
|
let added = self.added_ticks.swap_remove(row.as_usize()).into_inner();
|
||||||
let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner();
|
let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner();
|
||||||
(data, ComponentTicks { added, changed })
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = self.changed_by.swap_remove(row.as_usize()).into_inner();
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
let caller = ();
|
||||||
|
(data, ComponentTicks { added, changed }, caller)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the element from `other` at `src_row` and inserts it
|
/// Removes the element from `other` at `src_row` and inserts it
|
||||||
|
@ -281,16 +315,28 @@ impl Column {
|
||||||
other.added_ticks.swap_remove(src_row.as_usize());
|
other.added_ticks.swap_remove(src_row.as_usize());
|
||||||
*self.changed_ticks.get_unchecked_mut(dst_row.as_usize()) =
|
*self.changed_ticks.get_unchecked_mut(dst_row.as_usize()) =
|
||||||
other.changed_ticks.swap_remove(src_row.as_usize());
|
other.changed_ticks.swap_remove(src_row.as_usize());
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
{
|
||||||
|
*self.changed_by.get_unchecked_mut(dst_row.as_usize()) =
|
||||||
|
other.changed_by.swap_remove(src_row.as_usize());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a new value onto the end of the [`Column`].
|
/// Pushes a new value onto the end of the [`Column`].
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `ptr` must point to valid data of this column's component type
|
/// `ptr` must point to valid data of this column's component type
|
||||||
pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) {
|
pub(crate) unsafe fn push(
|
||||||
|
&mut self,
|
||||||
|
ptr: OwningPtr<'_>,
|
||||||
|
ticks: ComponentTicks,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||||
|
) {
|
||||||
self.data.push(ptr);
|
self.data.push(ptr);
|
||||||
self.added_ticks.push(UnsafeCell::new(ticks.added));
|
self.added_ticks.push(UnsafeCell::new(ticks.added));
|
||||||
self.changed_ticks.push(UnsafeCell::new(ticks.changed));
|
self.changed_ticks.push(UnsafeCell::new(ticks.changed));
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.changed_by.push(UnsafeCell::new(caller));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -298,6 +344,8 @@ impl Column {
|
||||||
self.data.reserve_exact(additional);
|
self.data.reserve_exact(additional);
|
||||||
self.added_ticks.reserve_exact(additional);
|
self.added_ticks.reserve_exact(additional);
|
||||||
self.changed_ticks.reserve_exact(additional);
|
self.changed_ticks.reserve_exact(additional);
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.changed_by.reserve_exact(additional);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the data pointer to the first element of the [`Column`].
|
/// Fetches the data pointer to the first element of the [`Column`].
|
||||||
|
@ -342,11 +390,25 @@ impl Column {
|
||||||
&self.changed_ticks
|
&self.changed_ticks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the slice to the [`Column`]'s caller locations.
|
||||||
|
///
|
||||||
|
/// Note: The values stored within are [`UnsafeCell`].
|
||||||
|
/// Users of this API must ensure that accesses to each individual element
|
||||||
|
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub fn get_changed_by_slice(&self) -> &[UnsafeCell<&'static Location<'static>>] {
|
||||||
|
&self.changed_by
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetches a reference to the data and change detection ticks at `row`.
|
/// Fetches a reference to the data and change detection ticks at `row`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if `row` is out of bounds.
|
/// Returns `None` if `row` is out of bounds.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
row: TableRow,
|
||||||
|
) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> {
|
||||||
(row.as_usize() < self.data.len())
|
(row.as_usize() < self.data.len())
|
||||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||||
// accessed through a read-only reference to the column.
|
// accessed through a read-only reference to the column.
|
||||||
|
@ -357,6 +419,10 @@ impl Column {
|
||||||
added: self.added_ticks.get_unchecked(row.as_usize()),
|
added: self.added_ticks.get_unchecked(row.as_usize()),
|
||||||
changed: self.changed_ticks.get_unchecked(row.as_usize()),
|
changed: self.changed_ticks.get_unchecked(row.as_usize()),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.changed_by.get_unchecked(row.as_usize()),
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -483,6 +549,35 @@ impl Column {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the calling location that last changed the value at `row`.
|
||||||
|
///
|
||||||
|
/// Returns `None` if `row` is out of bounds.
|
||||||
|
///
|
||||||
|
/// Note: The values stored within are [`UnsafeCell`].
|
||||||
|
/// Users of this API must ensure that accesses to each individual element
|
||||||
|
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub fn get_changed_by(&self, row: TableRow) -> Option<&UnsafeCell<&'static Location<'static>>> {
|
||||||
|
self.changed_by.get(row.as_usize())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches the calling location that last changed the value at `row`.
|
||||||
|
///
|
||||||
|
/// Unlike [`Column::get_changed_by`] this function does not do any bounds checking.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `row` must be within the range `[0, self.len())`.
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub unsafe fn get_changed_by_unchecked(
|
||||||
|
&self,
|
||||||
|
row: TableRow,
|
||||||
|
) -> &UnsafeCell<&'static Location<'static>> {
|
||||||
|
debug_assert!(row.as_usize() < self.changed_by.len());
|
||||||
|
self.changed_by.get_unchecked(row.as_usize())
|
||||||
|
}
|
||||||
|
|
||||||
/// Clears the column, removing all values.
|
/// Clears the column, removing all values.
|
||||||
///
|
///
|
||||||
/// Note that this function has no effect on the allocated capacity of the [`Column`]>
|
/// Note that this function has no effect on the allocated capacity of the [`Column`]>
|
||||||
|
@ -490,6 +585,8 @@ impl Column {
|
||||||
self.data.clear();
|
self.data.clear();
|
||||||
self.added_ticks.clear();
|
self.added_ticks.clear();
|
||||||
self.changed_ticks.clear();
|
self.changed_ticks.clear();
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.changed_by.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -608,7 +705,7 @@ impl Table {
|
||||||
new_column.initialize_from_unchecked(column, row, new_row);
|
new_column.initialize_from_unchecked(column, row, new_row);
|
||||||
} else {
|
} else {
|
||||||
// It's the caller's responsibility to drop these cases.
|
// It's the caller's responsibility to drop these cases.
|
||||||
let (_, _) = column.swap_remove_and_forget_unchecked(row);
|
let (_, _, _) = column.swap_remove_and_forget_unchecked(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TableMoveResult {
|
TableMoveResult {
|
||||||
|
@ -740,6 +837,8 @@ impl Table {
|
||||||
column.data.set_len(self.entities.len());
|
column.data.set_len(self.entities.len());
|
||||||
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
|
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
|
||||||
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
|
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
column.changed_by.push(UnsafeCell::new(Location::caller()));
|
||||||
}
|
}
|
||||||
TableRow::from_usize(index)
|
TableRow::from_usize(index)
|
||||||
}
|
}
|
||||||
|
@ -924,6 +1023,9 @@ mod tests {
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
storage::{TableBuilder, TableRow},
|
storage::{TableBuilder, TableRow},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct W<T>(T);
|
struct W<T>(T);
|
||||||
|
|
||||||
|
@ -947,6 +1049,8 @@ mod tests {
|
||||||
row,
|
row,
|
||||||
value_ptr,
|
value_ptr,
|
||||||
Tick::new(0),
|
Tick::new(0),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
Location::caller(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
mod parallel_scope;
|
mod parallel_scope;
|
||||||
|
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use core::panic::Location;
|
||||||
|
|
||||||
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
|
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
|
||||||
use crate::{
|
use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
|
@ -9,8 +12,10 @@ use crate::{
|
||||||
event::Event,
|
event::Event,
|
||||||
observer::{Observer, TriggerEvent, TriggerTargets},
|
observer::{Observer, TriggerEvent, TriggerTargets},
|
||||||
system::{RunSystemWithInput, SystemId},
|
system::{RunSystemWithInput, SystemId},
|
||||||
world::command_queue::RawCommandQueue,
|
world::{
|
||||||
world::{Command, CommandQueue, EntityWorldMut, FromWorld, World},
|
command_queue::RawCommandQueue, Command, CommandQueue, EntityWorldMut, FromWorld,
|
||||||
|
SpawnBatchIter, World,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use bevy_ptr::OwningPtr;
|
use bevy_ptr::OwningPtr;
|
||||||
use bevy_utils::tracing::{error, info};
|
use bevy_utils::tracing::{error, info};
|
||||||
|
@ -352,6 +357,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
///
|
///
|
||||||
/// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components.
|
/// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components.
|
||||||
/// - [`spawn_batch`](Self::spawn_batch) to spawn entities with a bundle each.
|
/// - [`spawn_batch`](Self::spawn_batch) to spawn entities with a bundle each.
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
|
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
|
||||||
let mut e = self.spawn_empty();
|
let mut e = self.spawn_empty();
|
||||||
e.insert(bundle);
|
e.insert(bundle);
|
||||||
|
@ -488,6 +494,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
///
|
///
|
||||||
/// - [`spawn`](Self::spawn) to spawn an entity with a bundle.
|
/// - [`spawn`](Self::spawn) to spawn an entity with a bundle.
|
||||||
/// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components.
|
/// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components.
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn_batch<I>(&mut self, bundles_iter: I)
|
pub fn spawn_batch<I>(&mut self, bundles_iter: I)
|
||||||
where
|
where
|
||||||
I: IntoIterator + Send + Sync + 'static,
|
I: IntoIterator + Send + Sync + 'static,
|
||||||
|
@ -533,6 +540,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`].
|
/// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`].
|
||||||
/// This method should generally only be used for sharing entities across apps, and only when they have a scheme
|
/// This method should generally only be used for sharing entities across apps, and only when they have a scheme
|
||||||
/// worked out to share an ID space (which doesn't happen by default).
|
/// worked out to share an ID space (which doesn't happen by default).
|
||||||
|
#[track_caller]
|
||||||
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I)
|
pub fn insert_or_spawn_batch<I, B>(&mut self, bundles_iter: I)
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||||
|
@ -565,6 +573,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
/// # }
|
/// # }
|
||||||
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
|
/// # bevy_ecs::system::assert_is_system(initialise_scoreboard);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn init_resource<R: Resource + FromWorld>(&mut self) {
|
pub fn init_resource<R: Resource + FromWorld>(&mut self) {
|
||||||
self.push(init_resource::<R>);
|
self.push(init_resource::<R>);
|
||||||
}
|
}
|
||||||
|
@ -594,6 +603,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||||
/// # }
|
/// # }
|
||||||
/// # bevy_ecs::system::assert_is_system(system);
|
/// # bevy_ecs::system::assert_is_system(system);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn insert_resource<R: Resource>(&mut self, resource: R) {
|
pub fn insert_resource<R: Resource>(&mut self, resource: R) {
|
||||||
self.push(insert_resource(resource));
|
self.push(insert_resource(resource));
|
||||||
}
|
}
|
||||||
|
@ -934,6 +944,7 @@ impl EntityCommands<'_> {
|
||||||
/// }
|
/// }
|
||||||
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
|
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||||
self.add(insert(bundle))
|
self.add(insert(bundle))
|
||||||
}
|
}
|
||||||
|
@ -1030,6 +1041,7 @@ impl EntityCommands<'_> {
|
||||||
/// }
|
/// }
|
||||||
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
|
/// # bevy_ecs::system::assert_is_system(add_combat_stats_system);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||||
self.add(try_insert(bundle))
|
self.add(try_insert(bundle))
|
||||||
}
|
}
|
||||||
|
@ -1236,13 +1248,21 @@ where
|
||||||
/// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities.
|
/// A [`Command`] that consumes an iterator of [`Bundle`]s to spawn a series of entities.
|
||||||
///
|
///
|
||||||
/// This is more efficient than spawning the entities individually.
|
/// This is more efficient than spawning the entities individually.
|
||||||
|
#[track_caller]
|
||||||
fn spawn_batch<I, B>(bundles_iter: I) -> impl Command
|
fn spawn_batch<I, B>(bundles_iter: I) -> impl Command
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = B> + Send + Sync + 'static,
|
I: IntoIterator<Item = B> + Send + Sync + 'static,
|
||||||
B: Bundle,
|
B: Bundle,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
move |world: &mut World| {
|
move |world: &mut World| {
|
||||||
world.spawn_batch(bundles_iter);
|
SpawnBatchIter::new(
|
||||||
|
world,
|
||||||
|
bundles_iter.into_iter(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1250,13 +1270,20 @@ where
|
||||||
/// If any entities do not already exist in the world, they will be spawned.
|
/// If any entities do not already exist in the world, they will be spawned.
|
||||||
///
|
///
|
||||||
/// This is more efficient than inserting the bundles individually.
|
/// This is more efficient than inserting the bundles individually.
|
||||||
|
#[track_caller]
|
||||||
fn insert_or_spawn_batch<I, B>(bundles_iter: I) -> impl Command
|
fn insert_or_spawn_batch<I, B>(bundles_iter: I) -> impl Command
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||||
B: Bundle,
|
B: Bundle,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = core::panic::Location::caller();
|
||||||
move |world: &mut World| {
|
move |world: &mut World| {
|
||||||
if let Err(invalid_entities) = world.insert_or_spawn_batch(bundles_iter) {
|
if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(
|
||||||
|
bundles_iter,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
) {
|
||||||
error!(
|
error!(
|
||||||
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
||||||
std::any::type_name::<B>(),
|
std::any::type_name::<B>(),
|
||||||
|
@ -1278,10 +1305,17 @@ fn despawn(entity: Entity, world: &mut World) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
|
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
|
||||||
|
#[track_caller]
|
||||||
fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
|
fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = core::panic::Location::caller();
|
||||||
move |entity: Entity, world: &mut World| {
|
move |entity: Entity, world: &mut World| {
|
||||||
if let Some(mut entity) = world.get_entity_mut(entity) {
|
if let Some(mut entity) = world.get_entity_mut(entity) {
|
||||||
entity.insert(bundle);
|
entity.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::<T>(), entity);
|
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::<T>(), entity);
|
||||||
}
|
}
|
||||||
|
@ -1289,10 +1323,17 @@ fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
|
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
|
||||||
|
#[track_caller]
|
||||||
fn try_insert(bundle: impl Bundle) -> impl EntityCommand {
|
fn try_insert(bundle: impl Bundle) -> impl EntityCommand {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = core::panic::Location::caller();
|
||||||
move |entity, world: &mut World| {
|
move |entity, world: &mut World| {
|
||||||
if let Some(mut entity) = world.get_entity_mut(entity) {
|
if let Some(mut entity) = world.get_entity_mut(entity) {
|
||||||
entity.insert(bundle);
|
entity.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1363,19 +1404,28 @@ fn retain<T: Bundle>(entity: Entity, world: &mut World) {
|
||||||
|
|
||||||
/// A [`Command`] that inserts a [`Resource`] into the world using a value
|
/// A [`Command`] that inserts a [`Resource`] into the world using a value
|
||||||
/// created with the [`FromWorld`] trait.
|
/// created with the [`FromWorld`] trait.
|
||||||
|
#[track_caller]
|
||||||
fn init_resource<R: Resource + FromWorld>(world: &mut World) {
|
fn init_resource<R: Resource + FromWorld>(world: &mut World) {
|
||||||
world.init_resource::<R>();
|
world.init_resource::<R>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Command`] that removes the [resource](Resource) `R` from the world.
|
/// A [`Command`] that removes the [resource](Resource) `R` from the world.
|
||||||
|
#[track_caller]
|
||||||
fn remove_resource<R: Resource>(world: &mut World) {
|
fn remove_resource<R: Resource>(world: &mut World) {
|
||||||
world.remove_resource::<R>();
|
world.remove_resource::<R>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Command`] that inserts a [`Resource`] into the world.
|
/// A [`Command`] that inserts a [`Resource`] into the world.
|
||||||
|
#[track_caller]
|
||||||
fn insert_resource<R: Resource>(resource: R) -> impl Command {
|
fn insert_resource<R: Resource>(resource: R) -> impl Command {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
move |world: &mut World| {
|
move |world: &mut World| {
|
||||||
world.insert_resource(resource);
|
world.insert_resource_with_caller(
|
||||||
|
resource,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ pub use bevy_ecs_macros::Resource;
|
||||||
pub use bevy_ecs_macros::SystemParam;
|
pub use bevy_ecs_macros::SystemParam;
|
||||||
use bevy_ptr::UnsafeCellDeref;
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
use bevy_utils::{all_tuples, synccell::SyncCell};
|
use bevy_utils::{all_tuples, synccell::SyncCell};
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
@ -528,15 +530,16 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
let (ptr, ticks) = world
|
let (ptr, ticks, _caller) =
|
||||||
.get_resource_with_ticks(component_id)
|
world
|
||||||
.unwrap_or_else(|| {
|
.get_resource_with_ticks(component_id)
|
||||||
panic!(
|
.unwrap_or_else(|| {
|
||||||
"Resource requested by {} does not exist: {}",
|
panic!(
|
||||||
system_meta.name,
|
"Resource requested by {} does not exist: {}",
|
||||||
std::any::type_name::<T>()
|
system_meta.name,
|
||||||
)
|
std::any::type_name::<T>()
|
||||||
});
|
)
|
||||||
|
});
|
||||||
Res {
|
Res {
|
||||||
value: ptr.deref(),
|
value: ptr.deref(),
|
||||||
ticks: Ticks {
|
ticks: Ticks {
|
||||||
|
@ -545,6 +548,8 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,7 +575,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world
|
world
|
||||||
.get_resource_with_ticks(component_id)
|
.get_resource_with_ticks(component_id)
|
||||||
.map(|(ptr, ticks)| Res {
|
.map(|(ptr, ticks, _caller)| Res {
|
||||||
value: ptr.deref(),
|
value: ptr.deref(),
|
||||||
ticks: Ticks {
|
ticks: Ticks {
|
||||||
added: ticks.added.deref(),
|
added: ticks.added.deref(),
|
||||||
|
@ -578,6 +583,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -640,6 +647,8 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: value.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,6 +679,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: value.changed_by,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1071,6 +1082,8 @@ pub struct NonSend<'w, T: 'static> {
|
||||||
ticks: ComponentTicks,
|
ticks: ComponentTicks,
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: Only reads a single World non-send resource
|
// SAFETY: Only reads a single World non-send resource
|
||||||
|
@ -1095,6 +1108,12 @@ impl<'w, T: 'static> NonSend<'w, T> {
|
||||||
pub fn is_changed(&self) -> bool {
|
pub fn is_changed(&self) -> bool {
|
||||||
self.ticks.is_changed(self.last_run, self.this_run)
|
self.ticks.is_changed(self.last_run, self.this_run)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The location that last caused this to change.
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
pub fn changed_by(&self) -> &'static Location<'static> {
|
||||||
|
self.changed_by
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, T> Deref for NonSend<'w, T> {
|
impl<'w, T> Deref for NonSend<'w, T> {
|
||||||
|
@ -1114,6 +1133,8 @@ impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
|
||||||
},
|
},
|
||||||
this_run: nsm.ticks.this_run,
|
this_run: nsm.ticks.this_run,
|
||||||
last_run: nsm.ticks.last_run,
|
last_run: nsm.ticks.last_run,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: nsm.changed_by,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1158,21 +1179,24 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
let (ptr, ticks) = world
|
let (ptr, ticks, _caller) =
|
||||||
.get_non_send_with_ticks(component_id)
|
world
|
||||||
.unwrap_or_else(|| {
|
.get_non_send_with_ticks(component_id)
|
||||||
panic!(
|
.unwrap_or_else(|| {
|
||||||
"Non-send resource requested by {} does not exist: {}",
|
panic!(
|
||||||
system_meta.name,
|
"Non-send resource requested by {} does not exist: {}",
|
||||||
std::any::type_name::<T>()
|
system_meta.name,
|
||||||
)
|
std::any::type_name::<T>()
|
||||||
});
|
)
|
||||||
|
});
|
||||||
|
|
||||||
NonSend {
|
NonSend {
|
||||||
value: ptr.deref(),
|
value: ptr.deref(),
|
||||||
ticks: ticks.read(),
|
ticks: ticks.read(),
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1198,11 +1222,13 @@ unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world
|
world
|
||||||
.get_non_send_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.map(|(ptr, ticks)| NonSend {
|
.map(|(ptr, ticks, _caller)| NonSend {
|
||||||
value: ptr.deref(),
|
value: ptr.deref(),
|
||||||
ticks: ticks.read(),
|
ticks: ticks.read(),
|
||||||
last_run: system_meta.last_run,
|
last_run: system_meta.last_run,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1250,18 +1276,21 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
let (ptr, ticks) = world
|
let (ptr, ticks, _caller) =
|
||||||
.get_non_send_with_ticks(component_id)
|
world
|
||||||
.unwrap_or_else(|| {
|
.get_non_send_with_ticks(component_id)
|
||||||
panic!(
|
.unwrap_or_else(|| {
|
||||||
"Non-send resource requested by {} does not exist: {}",
|
panic!(
|
||||||
system_meta.name,
|
"Non-send resource requested by {} does not exist: {}",
|
||||||
std::any::type_name::<T>()
|
system_meta.name,
|
||||||
)
|
std::any::type_name::<T>()
|
||||||
});
|
)
|
||||||
|
});
|
||||||
NonSendMut {
|
NonSendMut {
|
||||||
value: ptr.assert_unique().deref_mut(),
|
value: ptr.assert_unique().deref_mut(),
|
||||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1284,9 +1313,11 @@ unsafe impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world
|
world
|
||||||
.get_non_send_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.map(|(ptr, ticks)| NonSendMut {
|
.map(|(ptr, ticks, _caller)| NonSendMut {
|
||||||
value: ptr.assert_unique().deref_mut(),
|
value: ptr.assert_unique().deref_mut(),
|
||||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref_mut(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -768,12 +768,31 @@ impl<'w> EntityWorldMut<'w> {
|
||||||
/// Adds a [`Bundle`] of components to the entity.
|
/// Adds a [`Bundle`] of components to the entity.
|
||||||
///
|
///
|
||||||
/// This will overwrite any previous value(s) of the same component type.
|
/// This will overwrite any previous value(s) of the same component type.
|
||||||
|
#[track_caller]
|
||||||
pub fn insert<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
pub fn insert<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
||||||
|
self.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
core::panic::Location::caller(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split into a new function so we can pass the calling location into the function when using
|
||||||
|
/// as a command.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn insert_with_caller<T: Bundle>(
|
||||||
|
&mut self,
|
||||||
|
bundle: T,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location,
|
||||||
|
) -> &mut Self {
|
||||||
let change_tick = self.world.change_tick();
|
let change_tick = self.world.change_tick();
|
||||||
let mut bundle_inserter =
|
let mut bundle_inserter =
|
||||||
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick);
|
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick);
|
||||||
// SAFETY: location matches current entity. `T` matches `bundle_info`
|
self.location =
|
||||||
self.location = unsafe { bundle_inserter.insert(self.entity, self.location, bundle) };
|
// SAFETY: location matches current entity. `T` matches `bundle_info`
|
||||||
|
unsafe {
|
||||||
|
bundle_inserter.insert(self.entity, self.location, bundle, #[cfg(feature = "track_change_detection")] caller)
|
||||||
|
};
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,6 +806,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||||
///
|
///
|
||||||
/// - [`ComponentId`] must be from the same world as [`EntityWorldMut`]
|
/// - [`ComponentId`] must be from the same world as [`EntityWorldMut`]
|
||||||
/// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
/// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
||||||
|
#[track_caller]
|
||||||
pub unsafe fn insert_by_id(
|
pub unsafe fn insert_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
|
@ -828,6 +848,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// - Each [`ComponentId`] must be from the same world as [`EntityWorldMut`]
|
/// - Each [`ComponentId`] must be from the same world as [`EntityWorldMut`]
|
||||||
/// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
/// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
||||||
|
#[track_caller]
|
||||||
pub unsafe fn insert_by_ids<'a, I: Iterator<Item = OwningPtr<'a>>>(
|
pub unsafe fn insert_by_ids<'a, I: Iterator<Item = OwningPtr<'a>>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_ids: &[ComponentId],
|
component_ids: &[ComponentId],
|
||||||
|
@ -2300,6 +2321,7 @@ pub enum TryFromFilteredError {
|
||||||
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
||||||
/// [`BundleInfo`] used to construct [`BundleInserter`]
|
/// [`BundleInfo`] used to construct [`BundleInserter`]
|
||||||
/// - [`Entity`] must correspond to [`EntityLocation`]
|
/// - [`Entity`] must correspond to [`EntityLocation`]
|
||||||
|
#[track_caller]
|
||||||
unsafe fn insert_dynamic_bundle<
|
unsafe fn insert_dynamic_bundle<
|
||||||
'a,
|
'a,
|
||||||
I: Iterator<Item = OwningPtr<'a>>,
|
I: Iterator<Item = OwningPtr<'a>>,
|
||||||
|
@ -2328,7 +2350,15 @@ unsafe fn insert_dynamic_bundle<
|
||||||
};
|
};
|
||||||
|
|
||||||
// SAFETY: location matches current entity.
|
// SAFETY: location matches current entity.
|
||||||
unsafe { bundle_inserter.insert(entity, location, bundle) }
|
unsafe {
|
||||||
|
bundle_inserter.insert(
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
core::panic::Location::caller(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
|
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
|
||||||
|
|
|
@ -52,6 +52,12 @@ use std::{
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::atomic::{AtomicU32, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use core::panic::Location;
|
||||||
|
|
||||||
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
|
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
|
||||||
|
|
||||||
/// A [`World`] mutation.
|
/// A [`World`] mutation.
|
||||||
|
@ -971,6 +977,7 @@ impl World {
|
||||||
/// let position = world.entity(entity).get::<Position>().unwrap();
|
/// let position = world.entity(entity).get::<Position>().unwrap();
|
||||||
/// assert_eq!(position.x, 2.0);
|
/// assert_eq!(position.x, 2.0);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
|
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
|
||||||
self.flush();
|
self.flush();
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
@ -978,7 +985,14 @@ impl World {
|
||||||
let entity_location = {
|
let entity_location = {
|
||||||
let mut bundle_spawner = BundleSpawner::new::<B>(self, change_tick);
|
let mut bundle_spawner = BundleSpawner::new::<B>(self, change_tick);
|
||||||
// SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent
|
// SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent
|
||||||
unsafe { bundle_spawner.spawn_non_existent(entity, bundle) }
|
unsafe {
|
||||||
|
bundle_spawner.spawn_non_existent(
|
||||||
|
entity,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
Location::caller(),
|
||||||
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// SAFETY: entity and location are valid, as they were just created above
|
// SAFETY: entity and location are valid, as they were just created above
|
||||||
|
@ -1023,12 +1037,18 @@ impl World {
|
||||||
///
|
///
|
||||||
/// assert_eq!(entities.len(), 2);
|
/// assert_eq!(entities.len(), 2);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn_batch<I>(&mut self, iter: I) -> SpawnBatchIter<'_, I::IntoIter>
|
pub fn spawn_batch<I>(&mut self, iter: I) -> SpawnBatchIter<'_, I::IntoIter>
|
||||||
where
|
where
|
||||||
I: IntoIterator,
|
I: IntoIterator,
|
||||||
I::Item: Bundle,
|
I::Item: Bundle,
|
||||||
{
|
{
|
||||||
SpawnBatchIter::new(self, iter.into_iter())
|
SpawnBatchIter::new(
|
||||||
|
self,
|
||||||
|
iter.into_iter(),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
Location::caller(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a reference to the given `entity`'s [`Component`] of the given type.
|
/// Retrieves a reference to the given `entity`'s [`Component`] of the given type.
|
||||||
|
@ -1277,7 +1297,10 @@ impl World {
|
||||||
/// Note that any resource with the [`Default`] trait automatically implements [`FromWorld`],
|
/// Note that any resource with the [`Default`] trait automatically implements [`FromWorld`],
|
||||||
/// and those default values will be here instead.
|
/// and those default values will be here instead.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn init_resource<R: Resource + FromWorld>(&mut self) -> ComponentId {
|
pub fn init_resource<R: Resource + FromWorld>(&mut self) -> ComponentId {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
let component_id = self.components.init_resource::<R>();
|
let component_id = self.components.init_resource::<R>();
|
||||||
if self
|
if self
|
||||||
.storages
|
.storages
|
||||||
|
@ -1289,7 +1312,12 @@ impl World {
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.insert_resource_by_id(component_id, ptr);
|
self.insert_resource_by_id(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1302,12 +1330,33 @@ impl World {
|
||||||
/// If you insert a resource of a type that already exists,
|
/// If you insert a resource of a type that already exists,
|
||||||
/// you will overwrite any existing data.
|
/// you will overwrite any existing data.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn insert_resource<R: Resource>(&mut self, value: R) {
|
pub fn insert_resource<R: Resource>(&mut self, value: R) {
|
||||||
|
self.insert_resource_with_caller(
|
||||||
|
value,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
Location::caller(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split into a new function so we can pass the calling location into the function when using
|
||||||
|
/// as a command.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn insert_resource_with_caller<R: Resource>(
|
||||||
|
&mut self,
|
||||||
|
value: R,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||||
|
) {
|
||||||
let component_id = self.components.init_resource::<R>();
|
let component_id = self.components.init_resource::<R>();
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.insert_resource_by_id(component_id, ptr);
|
self.insert_resource_by_id(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1324,7 +1373,10 @@ impl World {
|
||||||
///
|
///
|
||||||
/// Panics if called from a thread other than the main thread.
|
/// Panics if called from a thread other than the main thread.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> ComponentId {
|
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> ComponentId {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
let component_id = self.components.init_non_send::<R>();
|
let component_id = self.components.init_non_send::<R>();
|
||||||
if self
|
if self
|
||||||
.storages
|
.storages
|
||||||
|
@ -1336,7 +1388,12 @@ impl World {
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.insert_non_send_by_id(component_id, ptr);
|
self.insert_non_send_by_id(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1353,12 +1410,20 @@ impl World {
|
||||||
/// If a value is already present, this function will panic if called
|
/// If a value is already present, this function will panic if called
|
||||||
/// from a different thread than where the original value was inserted from.
|
/// from a different thread than where the original value was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) {
|
pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
let component_id = self.components.init_non_send::<R>();
|
let component_id = self.components.init_non_send::<R>();
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||||
unsafe {
|
unsafe {
|
||||||
self.insert_non_send_by_id(component_id, ptr);
|
self.insert_non_send_by_id(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1367,7 +1432,7 @@ impl World {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
|
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
|
||||||
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||||
let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
let (ptr, _, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
||||||
// SAFETY: `component_id` was gotten via looking up the `R` type
|
// SAFETY: `component_id` was gotten via looking up the `R` type
|
||||||
unsafe { Some(ptr.read::<R>()) }
|
unsafe { Some(ptr.read::<R>()) }
|
||||||
}
|
}
|
||||||
|
@ -1386,7 +1451,7 @@ impl World {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> {
|
pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> {
|
||||||
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||||
let (ptr, _) = self
|
let (ptr, _, _) = self
|
||||||
.storages
|
.storages
|
||||||
.non_send_resources
|
.non_send_resources
|
||||||
.get_mut(component_id)?
|
.get_mut(component_id)?
|
||||||
|
@ -1603,10 +1668,13 @@ impl World {
|
||||||
/// Gets a mutable reference to the resource of type `T` if it exists,
|
/// Gets a mutable reference to the resource of type `T` if it exists,
|
||||||
/// otherwise inserts the resource using the result of calling `func`.
|
/// otherwise inserts the resource using the result of calling `func`.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn get_resource_or_insert_with<R: Resource>(
|
pub fn get_resource_or_insert_with<R: Resource>(
|
||||||
&mut self,
|
&mut self,
|
||||||
func: impl FnOnce() -> R,
|
func: impl FnOnce() -> R,
|
||||||
) -> Mut<'_, R> {
|
) -> Mut<'_, R> {
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = Location::caller();
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
let last_change_tick = self.last_change_tick();
|
let last_change_tick = self.last_change_tick();
|
||||||
|
|
||||||
|
@ -1616,7 +1684,12 @@ impl World {
|
||||||
OwningPtr::make(func(), |ptr| {
|
OwningPtr::make(func(), |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
// SAFETY: component_id was just initialized and corresponds to resource of type R.
|
||||||
unsafe {
|
unsafe {
|
||||||
data.insert(ptr, change_tick);
|
data.insert(
|
||||||
|
ptr,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1749,7 +1822,28 @@ impl World {
|
||||||
///
|
///
|
||||||
/// assert_eq!(world.get::<B>(e0), Some(&B(0.0)));
|
/// assert_eq!(world.get::<B>(e0), Some(&B(0.0)));
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn insert_or_spawn_batch<I, B>(&mut self, iter: I) -> Result<(), Vec<Entity>>
|
pub fn insert_or_spawn_batch<I, B>(&mut self, iter: I) -> Result<(), Vec<Entity>>
|
||||||
|
where
|
||||||
|
I: IntoIterator,
|
||||||
|
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||||
|
B: Bundle,
|
||||||
|
{
|
||||||
|
self.insert_or_spawn_batch_with_caller(
|
||||||
|
iter,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
Location::caller(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split into a new function so we can pass the calling location into the function when using
|
||||||
|
/// as a command.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn insert_or_spawn_batch_with_caller<I, B>(
|
||||||
|
&mut self,
|
||||||
|
iter: I,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||||
|
) -> Result<(), Vec<Entity>>
|
||||||
where
|
where
|
||||||
I: IntoIterator,
|
I: IntoIterator,
|
||||||
I::IntoIter: Iterator<Item = (Entity, B)>,
|
I::IntoIter: Iterator<Item = (Entity, B)>,
|
||||||
|
@ -1792,7 +1886,15 @@ impl World {
|
||||||
if location.archetype_id == archetype =>
|
if location.archetype_id == archetype =>
|
||||||
{
|
{
|
||||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||||
unsafe { inserter.insert(entity, location, bundle) };
|
unsafe {
|
||||||
|
inserter.insert(
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// SAFETY: we initialized this bundle_id in `init_info`
|
// SAFETY: we initialized this bundle_id in `init_info`
|
||||||
|
@ -1805,7 +1907,15 @@ impl World {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||||
unsafe { inserter.insert(entity, location, bundle) };
|
unsafe {
|
||||||
|
inserter.insert(
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
)
|
||||||
|
};
|
||||||
spawn_or_insert =
|
spawn_or_insert =
|
||||||
SpawnOrInsert::Insert(inserter, location.archetype_id);
|
SpawnOrInsert::Insert(inserter, location.archetype_id);
|
||||||
}
|
}
|
||||||
|
@ -1814,13 +1924,27 @@ impl World {
|
||||||
AllocAtWithoutReplacement::DidNotExist => {
|
AllocAtWithoutReplacement::DidNotExist => {
|
||||||
if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert {
|
if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert {
|
||||||
// SAFETY: `entity` is allocated (but non existent), bundle matches inserter
|
// SAFETY: `entity` is allocated (but non existent), bundle matches inserter
|
||||||
unsafe { spawner.spawn_non_existent(entity, bundle) };
|
unsafe {
|
||||||
|
spawner.spawn_non_existent(
|
||||||
|
entity,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
)
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: we initialized this bundle_id in `init_info`
|
// SAFETY: we initialized this bundle_id in `init_info`
|
||||||
let mut spawner =
|
let mut spawner =
|
||||||
unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) };
|
unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) };
|
||||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||||
unsafe { spawner.spawn_non_existent(entity, bundle) };
|
unsafe {
|
||||||
|
spawner.spawn_non_existent(
|
||||||
|
entity,
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
)
|
||||||
|
};
|
||||||
spawn_or_insert = SpawnOrInsert::Spawn(spawner);
|
spawn_or_insert = SpawnOrInsert::Spawn(spawner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1860,6 +1984,7 @@ impl World {
|
||||||
/// });
|
/// });
|
||||||
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
||||||
/// ```
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U {
|
pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U {
|
||||||
let last_change_tick = self.last_change_tick();
|
let last_change_tick = self.last_change_tick();
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
@ -1868,7 +1993,7 @@ impl World {
|
||||||
.components
|
.components
|
||||||
.get_resource_id(TypeId::of::<R>())
|
.get_resource_id(TypeId::of::<R>())
|
||||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||||
let (ptr, mut ticks) = self
|
let (ptr, mut ticks, mut _caller) = self
|
||||||
.storages
|
.storages
|
||||||
.resources
|
.resources
|
||||||
.get_mut(component_id)
|
.get_mut(component_id)
|
||||||
|
@ -1885,6 +2010,8 @@ impl World {
|
||||||
last_run: last_change_tick,
|
last_run: last_change_tick,
|
||||||
this_run: change_tick,
|
this_run: change_tick,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: &mut _caller,
|
||||||
};
|
};
|
||||||
let result = f(self, value_mut);
|
let result = f(self, value_mut);
|
||||||
assert!(!self.contains_resource::<R>(),
|
assert!(!self.contains_resource::<R>(),
|
||||||
|
@ -1898,7 +2025,14 @@ impl World {
|
||||||
self.storages
|
self.storages
|
||||||
.resources
|
.resources
|
||||||
.get_mut(component_id)
|
.get_mut(component_id)
|
||||||
.map(|info| info.insert_with_ticks(ptr, ticks))
|
.map(|info| {
|
||||||
|
info.insert_with_ticks(
|
||||||
|
ptr,
|
||||||
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
_caller,
|
||||||
|
);
|
||||||
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"No resource of type {} exists in the World.",
|
"No resource of type {} exists in the World.",
|
||||||
|
@ -1953,17 +2087,24 @@ impl World {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub unsafe fn insert_resource_by_id(
|
pub unsafe fn insert_resource_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
value: OwningPtr<'_>,
|
value: OwningPtr<'_>,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||||
) {
|
) {
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
let resource = self.initialize_resource_internal(component_id);
|
let resource = self.initialize_resource_internal(component_id);
|
||||||
// SAFETY: `value` is valid for `component_id`, ensured by caller
|
// SAFETY: `value` is valid for `component_id`, ensured by caller
|
||||||
unsafe {
|
unsafe {
|
||||||
resource.insert(value, change_tick);
|
resource.insert(
|
||||||
|
value,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1980,17 +2121,24 @@ impl World {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub unsafe fn insert_non_send_by_id(
|
pub unsafe fn insert_non_send_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
value: OwningPtr<'_>,
|
value: OwningPtr<'_>,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||||
) {
|
) {
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
let resource = self.initialize_non_send_internal(component_id);
|
let resource = self.initialize_non_send_internal(component_id);
|
||||||
// SAFETY: `value` is valid for `component_id`, ensured by caller
|
// SAFETY: `value` is valid for `component_id`, ensured by caller
|
||||||
unsafe {
|
unsafe {
|
||||||
resource.insert(value, change_tick);
|
resource.insert(
|
||||||
|
value,
|
||||||
|
change_tick,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2513,7 +2661,7 @@ impl World {
|
||||||
.get_info(component_id)
|
.get_info(component_id)
|
||||||
.debug_checked_unwrap()
|
.debug_checked_unwrap()
|
||||||
};
|
};
|
||||||
let (ptr, ticks) = data.get_with_ticks()?;
|
let (ptr, ticks, _caller) = data.get_with_ticks()?;
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells`
|
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells`
|
||||||
|
@ -2532,6 +2680,11 @@ impl World {
|
||||||
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one
|
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one
|
||||||
value: unsafe { ptr.assert_unique() },
|
value: unsafe { ptr.assert_unique() },
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY:
|
||||||
|
// - We have exclusive access to the world, so no other code can be aliasing the `Ptr`
|
||||||
|
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one
|
||||||
|
changed_by: unsafe { _caller.deref_mut() },
|
||||||
};
|
};
|
||||||
|
|
||||||
Some((component_info, mut_untyped))
|
Some((component_info, mut_untyped))
|
||||||
|
@ -3088,7 +3241,12 @@ mod tests {
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: value is valid for the component layout
|
// SAFETY: value is valid for the component layout
|
||||||
unsafe {
|
unsafe {
|
||||||
world.insert_resource_by_id(component_id, ptr);
|
world.insert_resource_by_id(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
core::panic::Location::caller(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::{
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use std::panic::Location;
|
||||||
|
|
||||||
/// An iterator that spawns a series of entities and returns the [ID](Entity) of
|
/// An iterator that spawns a series of entities and returns the [ID](Entity) of
|
||||||
/// each spawned entity.
|
/// each spawned entity.
|
||||||
|
@ -16,6 +18,8 @@ where
|
||||||
{
|
{
|
||||||
inner: I,
|
inner: I,
|
||||||
spawner: BundleSpawner<'w>,
|
spawner: BundleSpawner<'w>,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller: &'static Location<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, I> SpawnBatchIter<'w, I>
|
impl<'w, I> SpawnBatchIter<'w, I>
|
||||||
|
@ -24,7 +28,12 @@ where
|
||||||
I::Item: Bundle,
|
I::Item: Bundle,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
|
#[track_caller]
|
||||||
|
pub(crate) fn new(
|
||||||
|
world: &'w mut World,
|
||||||
|
iter: I,
|
||||||
|
#[cfg(feature = "track_change_detection")] caller: &'static Location,
|
||||||
|
) -> Self {
|
||||||
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
|
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
|
||||||
// necessary
|
// necessary
|
||||||
world.flush();
|
world.flush();
|
||||||
|
@ -41,6 +50,8 @@ where
|
||||||
Self {
|
Self {
|
||||||
inner: iter,
|
inner: iter,
|
||||||
spawner,
|
spawner,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
caller,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +80,13 @@ where
|
||||||
fn next(&mut self) -> Option<Entity> {
|
fn next(&mut self) -> Option<Entity> {
|
||||||
let bundle = self.inner.next()?;
|
let bundle = self.inner.next()?;
|
||||||
// SAFETY: bundle matches spawner type
|
// SAFETY: bundle matches spawner type
|
||||||
unsafe { Some(self.spawner.spawn(bundle)) }
|
unsafe {
|
||||||
|
Some(self.spawner.spawn(
|
||||||
|
bundle,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
self.caller,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::{Mut, Ref, World, WorldId};
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::{Archetype, Archetypes},
|
archetype::{Archetype, Archetypes},
|
||||||
bundle::Bundles,
|
bundle::Bundles,
|
||||||
change_detection::{MutUntyped, Ticks, TicksMut},
|
change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut},
|
||||||
component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells},
|
component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells},
|
||||||
entity::{Entities, Entity, EntityLocation},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
|
@ -17,6 +17,8 @@ use crate::{
|
||||||
world::RawCommandQueue,
|
world::RawCommandQueue,
|
||||||
};
|
};
|
||||||
use bevy_ptr::Ptr;
|
use bevy_ptr::Ptr;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr};
|
use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr};
|
||||||
|
|
||||||
/// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid
|
/// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid
|
||||||
|
@ -334,7 +336,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
|
|
||||||
// SAFETY: caller ensures `self` has permission to access the resource
|
// SAFETY: caller ensures `self` has permission to access the resource
|
||||||
// caller also ensure that no mutable reference to the resource exists
|
// caller also ensure that no mutable reference to the resource exists
|
||||||
let (ptr, ticks) = unsafe { self.get_resource_with_ticks(component_id)? };
|
let (ptr, ticks, _caller) = unsafe { self.get_resource_with_ticks(component_id)? };
|
||||||
|
|
||||||
// SAFETY: `component_id` was obtained from the type ID of `R`
|
// SAFETY: `component_id` was obtained from the type ID of `R`
|
||||||
let value = unsafe { ptr.deref::<R>() };
|
let value = unsafe { ptr.deref::<R>() };
|
||||||
|
@ -343,7 +345,16 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
let ticks =
|
let ticks =
|
||||||
unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), self.change_tick()) };
|
unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), self.change_tick()) };
|
||||||
|
|
||||||
Some(Res { value, ticks })
|
// SAFETY: caller ensures that no mutable reference to the resource exists
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
let caller = unsafe { _caller.deref() };
|
||||||
|
|
||||||
|
Some(Res {
|
||||||
|
value,
|
||||||
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: caller,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a pointer to the resource with the id [`ComponentId`] if it exists.
|
/// Gets a pointer to the resource with the id [`ComponentId`] if it exists.
|
||||||
|
@ -446,7 +457,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
) -> Option<MutUntyped<'w>> {
|
) -> Option<MutUntyped<'w>> {
|
||||||
// SAFETY: we only access data that the caller has ensured is unaliased and `self`
|
// SAFETY: we only access data that the caller has ensured is unaliased and `self`
|
||||||
// has permission to access.
|
// has permission to access.
|
||||||
let (ptr, ticks) = unsafe { self.storages() }
|
let (ptr, ticks, _caller) = unsafe { self.storages() }
|
||||||
.resources
|
.resources
|
||||||
.get(component_id)?
|
.get(component_id)?
|
||||||
.get_with_ticks()?;
|
.get_with_ticks()?;
|
||||||
|
@ -464,6 +475,11 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
// - caller ensures that the resource is unaliased
|
// - caller ensures that the resource is unaliased
|
||||||
value: unsafe { ptr.assert_unique() },
|
value: unsafe { ptr.assert_unique() },
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY:
|
||||||
|
// - caller ensures that `self` has permission to access the resource
|
||||||
|
// - caller ensures that the resource is unaliased
|
||||||
|
changed_by: unsafe { _caller.deref_mut() },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +524,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
// SAFETY: we only access data that the caller has ensured is unaliased and `self`
|
// SAFETY: we only access data that the caller has ensured is unaliased and `self`
|
||||||
// has permission to access.
|
// has permission to access.
|
||||||
let (ptr, ticks) = unsafe { self.storages() }
|
let (ptr, ticks, _caller) = unsafe { self.storages() }
|
||||||
.non_send_resources
|
.non_send_resources
|
||||||
.get(component_id)?
|
.get(component_id)?
|
||||||
.get_with_ticks()?;
|
.get_with_ticks()?;
|
||||||
|
@ -523,6 +539,9 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
// SAFETY: This function has exclusive access to the world so nothing aliases `ptr`.
|
// SAFETY: This function has exclusive access to the world so nothing aliases `ptr`.
|
||||||
value: unsafe { ptr.assert_unique() },
|
value: unsafe { ptr.assert_unique() },
|
||||||
ticks,
|
ticks,
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY: This function has exclusive access to the world
|
||||||
|
changed_by: unsafe { _caller.deref_mut() },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +555,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
pub(crate) unsafe fn get_resource_with_ticks(
|
pub(crate) unsafe fn get_resource_with_ticks(
|
||||||
self,
|
self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> Option<(Ptr<'w>, TickCells<'w>)> {
|
) -> Option<(Ptr<'w>, TickCells<'w>, MaybeUnsafeCellLocation<'w>)> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - caller ensures there is no `&mut World`
|
// - caller ensures there is no `&mut World`
|
||||||
// - caller ensures there are no mutable borrows of this resource
|
// - caller ensures there are no mutable borrows of this resource
|
||||||
|
@ -560,7 +579,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
||||||
pub(crate) unsafe fn get_non_send_with_ticks(
|
pub(crate) unsafe fn get_non_send_with_ticks(
|
||||||
self,
|
self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> Option<(Ptr<'w>, TickCells<'w>)> {
|
) -> Option<(Ptr<'w>, TickCells<'w>, MaybeUnsafeCellLocation<'w>)> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - caller ensures there is no `&mut World`
|
// - caller ensures there is no `&mut World`
|
||||||
// - caller ensures there are no mutable borrows of this resource
|
// - caller ensures there are no mutable borrows of this resource
|
||||||
|
@ -732,10 +751,12 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
self.entity,
|
self.entity,
|
||||||
self.location,
|
self.location,
|
||||||
)
|
)
|
||||||
.map(|(value, cells)| Ref {
|
.map(|(value, cells, _caller)| Ref {
|
||||||
// SAFETY: returned component is of type T
|
// SAFETY: returned component is of type T
|
||||||
value: value.deref::<T>(),
|
value: value.deref::<T>(),
|
||||||
ticks: Ticks::from_tick_cells(cells, last_change_tick, change_tick),
|
ticks: Ticks::from_tick_cells(cells, last_change_tick, change_tick),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -831,10 +852,12 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
self.entity,
|
self.entity,
|
||||||
self.location,
|
self.location,
|
||||||
)
|
)
|
||||||
.map(|(value, cells)| Mut {
|
.map(|(value, cells, _caller)| Mut {
|
||||||
// SAFETY: returned component is of type T
|
// SAFETY: returned component is of type T
|
||||||
value: value.assert_unique().deref_mut::<T>(),
|
value: value.assert_unique().deref_mut::<T>(),
|
||||||
ticks: TicksMut::from_tick_cells(cells, last_change_tick, change_tick),
|
ticks: TicksMut::from_tick_cells(cells, last_change_tick, change_tick),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref_mut(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -891,7 +914,7 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
self.entity,
|
self.entity,
|
||||||
self.location,
|
self.location,
|
||||||
)
|
)
|
||||||
.map(|(value, cells)| MutUntyped {
|
.map(|(value, cells, _caller)| MutUntyped {
|
||||||
// SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime
|
// SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime
|
||||||
value: value.assert_unique(),
|
value: value.assert_unique(),
|
||||||
ticks: TicksMut::from_tick_cells(
|
ticks: TicksMut::from_tick_cells(
|
||||||
|
@ -899,6 +922,8 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
self.world.last_change_tick(),
|
self.world.last_change_tick(),
|
||||||
self.world.change_tick(),
|
self.world.change_tick(),
|
||||||
),
|
),
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
changed_by: _caller.deref_mut(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -975,7 +1000,7 @@ unsafe fn get_component_and_ticks(
|
||||||
storage_type: StorageType,
|
storage_type: StorageType,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
location: EntityLocation,
|
location: EntityLocation,
|
||||||
) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> {
|
||||||
match storage_type {
|
match storage_type {
|
||||||
StorageType::Table => {
|
StorageType::Table => {
|
||||||
let components = world.fetch_table(location, component_id)?;
|
let components = world.fetch_table(location, component_id)?;
|
||||||
|
@ -987,6 +1012,10 @@ unsafe fn get_component_and_ticks(
|
||||||
added: components.get_added_tick_unchecked(location.table_row),
|
added: components.get_added_tick_unchecked(location.table_row),
|
||||||
changed: components.get_changed_tick_unchecked(location.table_row),
|
changed: components.get_changed_tick_unchecked(location.table_row),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
components.get_changed_by_unchecked(location.table_row),
|
||||||
|
#[cfg(not(feature = "track_change_detection"))]
|
||||||
|
(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_with_ticks(entity),
|
StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_with_ticks(entity),
|
||||||
|
|
|
@ -195,6 +195,9 @@ ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"]
|
||||||
# Enable built in global state machines
|
# Enable built in global state machines
|
||||||
bevy_state = ["dep:bevy_state"]
|
bevy_state = ["dep:bevy_state"]
|
||||||
|
|
||||||
|
# Enables source location tracking for change detection, which can assist with debugging
|
||||||
|
track_change_detection = ["bevy_ecs/track_change_detection"]
|
||||||
|
|
||||||
# Enable function reflection
|
# Enable function reflection
|
||||||
reflect_functions = ["bevy_reflect/functions"]
|
reflect_functions = ["bevy_reflect/functions"]
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ The default feature set enables most of the expected features of a game engine,
|
||||||
|trace_chrome|Tracing support, saving a file in Chrome Tracing format|
|
|trace_chrome|Tracing support, saving a file in Chrome Tracing format|
|
||||||
|trace_tracy|Tracing support, exposing a port for Tracy|
|
|trace_tracy|Tracing support, exposing a port for Tracy|
|
||||||
|trace_tracy_memory|Tracing support, with memory profiling, exposing a port for Tracy|
|
|trace_tracy_memory|Tracing support, with memory profiling, exposing a port for Tracy|
|
||||||
|
|track_change_detection|Enables source location tracking for change detection, which can assist with debugging|
|
||||||
|wav|WAV audio format support|
|
|wav|WAV audio format support|
|
||||||
|wayland|Wayland display server support|
|
|wayland|Wayland display server support|
|
||||||
|webgpu|Enable support for WebGPU in Wasm. When enabled, this feature will override the `webgl2` feature and you won't be able to run Wasm builds with WebGL2, only with WebGPU.|
|
|webgpu|Enable support for WebGPU in Wasm. When enabled, this feature will override the `webgl2` feature and you won't be able to run Wasm builds with WebGL2, only with WebGPU.|
|
||||||
|
|
|
@ -269,7 +269,7 @@ Example | Description
|
||||||
|
|
||||||
Example | Description
|
Example | Description
|
||||||
--- | ---
|
--- | ---
|
||||||
[Component Change Detection](../examples/ecs/component_change_detection.rs) | Change detection on components
|
[Change Detection](../examples/ecs/change_detection.rs) | Change detection on components and resources
|
||||||
[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events
|
[Component Hooks](../examples/ecs/component_hooks.rs) | Define component hooks to manage component lifecycle events
|
||||||
[Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type
|
[Custom Query Parameters](../examples/ecs/custom_query_param.rs) | Groups commonly used compound queries and query filters into a single type
|
||||||
[Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules
|
[Custom Schedule](../examples/ecs/custom_schedule.rs) | Demonstrates how to add custom schedules
|
||||||
|
|
106
examples/ecs/change_detection.rs
Normal file
106
examples/ecs/change_detection.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
//! This example illustrates how to react to component and resource changes.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
change_component,
|
||||||
|
change_component_2,
|
||||||
|
change_resource,
|
||||||
|
change_detection,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, PartialEq, Debug)]
|
||||||
|
struct MyComponent(f32);
|
||||||
|
|
||||||
|
#[derive(Resource, PartialEq, Debug)]
|
||||||
|
struct MyResource(f32);
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
// Note the first change detection log correctly points to this line because the component is
|
||||||
|
// added. Although commands are deferred, they are able to track the original calling location.
|
||||||
|
commands.spawn(MyComponent(0.0));
|
||||||
|
commands.insert_resource(MyResource(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
|
||||||
|
for (entity, mut component) in &mut query {
|
||||||
|
if rand::thread_rng().gen_bool(0.1) {
|
||||||
|
let new_component = MyComponent(time.elapsed_seconds().round());
|
||||||
|
info!("New value: {new_component:?} {entity:?}");
|
||||||
|
// Change detection occurs on mutable dereference, and does not consider whether or not
|
||||||
|
// a value is actually equal. To avoid triggering change detection when nothing has
|
||||||
|
// actually changed, you can use the `set_if_neq` method on any component or resource
|
||||||
|
// that implements PartialEq.
|
||||||
|
component.set_if_neq(new_component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a duplicate of the `change_component` system, added to show that change tracking can
|
||||||
|
/// help you find *where* your component is being changed, when there are multiple possible
|
||||||
|
/// locations.
|
||||||
|
fn change_component_2(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
|
||||||
|
for (entity, mut component) in &mut query {
|
||||||
|
if rand::thread_rng().gen_bool(0.1) {
|
||||||
|
let new_component = MyComponent(time.elapsed_seconds().round());
|
||||||
|
info!("New value: {new_component:?} {entity:?}");
|
||||||
|
component.set_if_neq(new_component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change detection concepts for components apply similarly to resources.
|
||||||
|
fn change_resource(time: Res<Time>, mut my_resource: ResMut<MyResource>) {
|
||||||
|
if rand::thread_rng().gen_bool(0.1) {
|
||||||
|
let new_resource = MyResource(time.elapsed_seconds().round());
|
||||||
|
info!("New value: {new_resource:?}");
|
||||||
|
my_resource.set_if_neq(new_resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Query filters like [`Changed<T>`] and [`Added<T>`] ensure only entities matching these filters
|
||||||
|
/// will be returned by the query.
|
||||||
|
///
|
||||||
|
/// Using the [`Ref<T>`] system param allows you to access change detection information, but does
|
||||||
|
/// not filter the query.
|
||||||
|
fn change_detection(
|
||||||
|
changed_components: Query<Ref<MyComponent>, Changed<MyComponent>>,
|
||||||
|
my_resource: Res<MyResource>,
|
||||||
|
) {
|
||||||
|
for component in &changed_components {
|
||||||
|
// By default, you can only tell that a component was changed.
|
||||||
|
//
|
||||||
|
// This is useful, but what if you have multiple systems modifying the same component, how
|
||||||
|
// will you know which system is causing the component to change?
|
||||||
|
warn!(
|
||||||
|
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
|
||||||
|
component,
|
||||||
|
component.is_added(),
|
||||||
|
component.is_changed(),
|
||||||
|
// If you enable the `track_change_detection` feature, you can unlock the `changed_by()`
|
||||||
|
// method. It returns the file and line number that the component or resource was
|
||||||
|
// changed in. It's not recommended for released games, but great for debugging!
|
||||||
|
component.changed_by()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if my_resource.is_changed() {
|
||||||
|
warn!(
|
||||||
|
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
|
||||||
|
my_resource,
|
||||||
|
my_resource.is_added(),
|
||||||
|
my_resource.is_changed(),
|
||||||
|
my_resource.changed_by() // Like components, requires `track_change_detection` feature.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
//! This example illustrates how to react to component change.
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_systems(Startup, setup)
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(change_component, change_detection, tracker_monitoring),
|
|
||||||
)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, PartialEq, Debug)]
|
|
||||||
struct MyComponent(f32);
|
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
|
||||||
commands.spawn(MyComponent(0.));
|
|
||||||
commands.spawn(Transform::IDENTITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
|
|
||||||
for (entity, mut component) in &mut query {
|
|
||||||
if rand::thread_rng().gen_bool(0.1) {
|
|
||||||
info!("changing component {:?}", entity);
|
|
||||||
let new_component = MyComponent(time.elapsed_seconds().round());
|
|
||||||
// Change detection occurs on mutable dereference,
|
|
||||||
// and does not consider whether or not a value is actually equal.
|
|
||||||
// To avoid triggering change detection when nothing has actually changed,
|
|
||||||
// you can use the `set_if_neq` method on any component or resource that implements PartialEq
|
|
||||||
component.set_if_neq(new_component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are query filters for `Changed<T>` and `Added<T>`
|
|
||||||
// Only entities matching the filters will be in the query
|
|
||||||
fn change_detection(query: Query<(Entity, &MyComponent), Changed<MyComponent>>) {
|
|
||||||
for (entity, component) in &query {
|
|
||||||
info!("{:?} changed: {:?}", entity, component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By using `Ref`, the query is not filtered but the information is available
|
|
||||||
fn tracker_monitoring(query: Query<(Entity, Ref<MyComponent>)>) {
|
|
||||||
for (entity, component) in &query {
|
|
||||||
info!(
|
|
||||||
"{:?}: {:?} -> {{is_added: {}, is_changed: {}}}",
|
|
||||||
entity,
|
|
||||||
component,
|
|
||||||
component.is_added(),
|
|
||||||
component.is_changed()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue