Track callsite for observers & hooks (#15607)
# Objective Fixes #14708 Also fixes some commands not updating tracked location. ## Solution `ObserverTrigger` has a new `caller` field with the `track_change_detection` feature; hooks take an additional caller parameter (which is `Some(…)` or `None` depending on the feature). ## Testing See the new tests in `src/observer/mod.rs` --- ## Showcase Observers now know from where they were triggered (if `track_change_detection` is enabled): ```rust world.observe(move |trigger: Trigger<OnAdd, Foo>| { println!("Added Foo from {}", trigger.caller()); }); ``` ## Migration - hooks now take an additional `Option<&'static Location>` argument --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
5c43890d49
commit
f32a6fb205
@ -70,10 +70,13 @@ impl Component for OrderIndependentTransparencySettings {
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_add(|world, entity, _| {
|
||||
hooks.on_add(|world, entity, _, caller| {
|
||||
if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) {
|
||||
if value.layer_count > 32 {
|
||||
warn!("OrderIndependentTransparencySettings layer_count set to {} might be too high.", value.layer_count);
|
||||
warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.",
|
||||
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
|
||||
value.layer_count
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1058,12 +1058,16 @@ impl<'w> BundleInserter<'w> {
|
||||
ON_REPLACE,
|
||||
entity,
|
||||
archetype_after_insert.iter_existing(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_replace(
|
||||
archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_existing(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1236,12 +1240,16 @@ impl<'w> BundleInserter<'w> {
|
||||
new_archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if new_archetype.has_add_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_ADD,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
match insert_mode {
|
||||
@ -1251,12 +1259,16 @@ impl<'w> BundleInserter<'w> {
|
||||
new_archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_inserted(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if new_archetype.has_insert_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
archetype_after_insert.iter_inserted(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1267,12 +1279,16 @@ impl<'w> BundleInserter<'w> {
|
||||
new_archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if new_archetype.has_insert_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1348,6 +1364,7 @@ impl<'w> BundleSpawner<'w> {
|
||||
/// # Safety
|
||||
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub unsafe fn spawn_non_existent<T: DynamicBundle>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
@ -1395,24 +1412,32 @@ impl<'w> BundleSpawner<'w> {
|
||||
archetype,
|
||||
entity,
|
||||
bundle_info.iter_contributed_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if archetype.has_add_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_ADD,
|
||||
entity,
|
||||
bundle_info.iter_contributed_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_insert(
|
||||
archetype,
|
||||
entity,
|
||||
bundle_info.iter_contributed_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if archetype.has_insert_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
bundle_info.iter_contributed_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1681,6 +1706,7 @@ mod tests {
|
||||
use crate as bevy_ecs;
|
||||
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
|
||||
use alloc::vec;
|
||||
use core::panic::Location;
|
||||
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
@ -1689,19 +1715,39 @@ mod tests {
|
||||
#[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)]
|
||||
struct AMacroHooks;
|
||||
|
||||
fn a_on_add(mut world: DeferredWorld, _: Entity, _: ComponentId) {
|
||||
fn a_on_add(
|
||||
mut world: DeferredWorld,
|
||||
_: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<R>().assert_order(0);
|
||||
}
|
||||
|
||||
fn a_on_insert<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||
fn a_on_insert<T1, T2>(
|
||||
mut world: DeferredWorld,
|
||||
_: T1,
|
||||
_: T2,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<R>().assert_order(1);
|
||||
}
|
||||
|
||||
fn a_on_replace<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||
fn a_on_replace<T1, T2>(
|
||||
mut world: DeferredWorld,
|
||||
_: T1,
|
||||
_: T2,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<R>().assert_order(2);
|
||||
}
|
||||
|
||||
fn a_on_remove<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||
fn a_on_remove<T1, T2>(
|
||||
mut world: DeferredWorld,
|
||||
_: T1,
|
||||
_: T2,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<R>().assert_order(3);
|
||||
}
|
||||
|
||||
@ -1734,10 +1780,10 @@ mod tests {
|
||||
world.init_resource::<R>();
|
||||
world
|
||||
.register_component_hooks::<A>()
|
||||
.on_add(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _| world.resource_mut::<R>().assert_order(1))
|
||||
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(2))
|
||||
.on_remove(|mut world, _, _| world.resource_mut::<R>().assert_order(3));
|
||||
.on_add(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _, _| world.resource_mut::<R>().assert_order(1))
|
||||
.on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(2))
|
||||
.on_remove(|mut world, _, _, _| world.resource_mut::<R>().assert_order(3));
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.despawn(entity);
|
||||
@ -1761,10 +1807,10 @@ mod tests {
|
||||
world.init_resource::<R>();
|
||||
world
|
||||
.register_component_hooks::<A>()
|
||||
.on_add(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _| world.resource_mut::<R>().assert_order(1))
|
||||
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(2))
|
||||
.on_remove(|mut world, _, _| world.resource_mut::<R>().assert_order(3));
|
||||
.on_add(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _, _| world.resource_mut::<R>().assert_order(1))
|
||||
.on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(2))
|
||||
.on_remove(|mut world, _, _, _| world.resource_mut::<R>().assert_order(3));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
entity.insert(A);
|
||||
@ -1778,8 +1824,8 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world
|
||||
.register_component_hooks::<A>()
|
||||
.on_replace(|mut world, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _| {
|
||||
.on_replace(|mut world, _, _, _| world.resource_mut::<R>().assert_order(0))
|
||||
.on_insert(|mut world, _, _, _| {
|
||||
if let Some(mut r) = world.get_resource_mut::<R>() {
|
||||
r.assert_order(1);
|
||||
}
|
||||
@ -1800,22 +1846,22 @@ mod tests {
|
||||
world.init_resource::<R>();
|
||||
world
|
||||
.register_component_hooks::<A>()
|
||||
.on_add(|mut world, entity, _| {
|
||||
.on_add(|mut world, entity, _, _| {
|
||||
world.resource_mut::<R>().assert_order(0);
|
||||
world.commands().entity(entity).insert(B);
|
||||
})
|
||||
.on_remove(|mut world, entity, _| {
|
||||
.on_remove(|mut world, entity, _, _| {
|
||||
world.resource_mut::<R>().assert_order(2);
|
||||
world.commands().entity(entity).remove::<B>();
|
||||
});
|
||||
|
||||
world
|
||||
.register_component_hooks::<B>()
|
||||
.on_add(|mut world, entity, _| {
|
||||
.on_add(|mut world, entity, _, _| {
|
||||
world.resource_mut::<R>().assert_order(1);
|
||||
world.commands().entity(entity).remove::<A>();
|
||||
})
|
||||
.on_remove(|mut world, _, _| {
|
||||
.on_remove(|mut world, _, _, _| {
|
||||
world.resource_mut::<R>().assert_order(3);
|
||||
});
|
||||
|
||||
@ -1832,27 +1878,27 @@ mod tests {
|
||||
world.init_resource::<R>();
|
||||
world
|
||||
.register_component_hooks::<A>()
|
||||
.on_add(|mut world, entity, _| {
|
||||
.on_add(|mut world, entity, _, _| {
|
||||
world.resource_mut::<R>().assert_order(0);
|
||||
world.commands().entity(entity).insert(B).insert(C);
|
||||
});
|
||||
|
||||
world
|
||||
.register_component_hooks::<B>()
|
||||
.on_add(|mut world, entity, _| {
|
||||
.on_add(|mut world, entity, _, _| {
|
||||
world.resource_mut::<R>().assert_order(1);
|
||||
world.commands().entity(entity).insert(D);
|
||||
});
|
||||
|
||||
world
|
||||
.register_component_hooks::<C>()
|
||||
.on_add(|mut world, _, _| {
|
||||
.on_add(|mut world, _, _, _| {
|
||||
world.resource_mut::<R>().assert_order(3);
|
||||
});
|
||||
|
||||
world
|
||||
.register_component_hooks::<D>()
|
||||
.on_add(|mut world, _, _| {
|
||||
.on_add(|mut world, _, _, _| {
|
||||
world.resource_mut::<R>().assert_order(2);
|
||||
});
|
||||
|
||||
|
||||
@ -21,8 +21,6 @@ use bevy_ptr::{OwningPtr, UnsafeCellDeref};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::{HashMap, HashSet, TypeIdMap};
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
use core::{
|
||||
alloc::Layout,
|
||||
any::{Any, TypeId},
|
||||
@ -30,6 +28,7 @@ use core::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
mem::needs_drop,
|
||||
panic::Location,
|
||||
};
|
||||
use disqualified::ShortName;
|
||||
use thiserror::Error;
|
||||
@ -304,6 +303,7 @@ pub use bevy_ecs_macros::require;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// # use bevy_ecs::entity::Entity;
|
||||
/// # use bevy_ecs::component::ComponentId;
|
||||
/// # use core::panic::Location;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(on_add = my_on_add_hook)]
|
||||
@ -315,12 +315,12 @@ pub use bevy_ecs_macros::require;
|
||||
/// // #[component(on_replace = my_on_replace_hook, on_remove = my_on_remove_hook)]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId) {
|
||||
/// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId, caller: Option<&Location>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// // You can also omit writing some types using generics.
|
||||
/// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2) {
|
||||
/// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2, caller: Option<&Location>) {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
@ -497,8 +497,10 @@ pub enum StorageType {
|
||||
SparseSet,
|
||||
}
|
||||
|
||||
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`
|
||||
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
|
||||
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
|
||||
/// The caller location is `Some` if the `track_caller` feature is enabled.
|
||||
pub type ComponentHook =
|
||||
for<'w> fn(DeferredWorld<'w>, Entity, ComponentId, Option<&'static Location<'static>>);
|
||||
|
||||
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
|
||||
///
|
||||
@ -535,12 +537,12 @@ pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
|
||||
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
|
||||
/// assert!(tracked_component_query.iter(&world).next().is_none());
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, entity, _component_id| {
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, entity, _component_id, _caller| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.insert(entity);
|
||||
/// });
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id| {
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, entity, _component_id, _caller| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.remove(&entity);
|
||||
/// });
|
||||
|
||||
@ -274,6 +274,7 @@ pub struct EntityCloner {
|
||||
|
||||
impl EntityCloner {
|
||||
/// Clones and inserts components from the `source` entity into `target` entity using the stored configuration.
|
||||
#[track_caller]
|
||||
pub fn clone_entity(&mut self, world: &mut World) {
|
||||
// SAFETY:
|
||||
// - `source_entity` is read-only.
|
||||
|
||||
@ -22,8 +22,8 @@ use crate::{
|
||||
};
|
||||
use alloc::{format, string::String, vec::Vec};
|
||||
use bevy_ecs_macros::VisitEntitiesMut;
|
||||
use core::ops::Deref;
|
||||
use core::slice;
|
||||
use core::{ops::Deref, panic::Location};
|
||||
use disqualified::ShortName;
|
||||
use log::warn;
|
||||
|
||||
@ -270,6 +270,7 @@ pub fn validate_parent_has_component<C: Component>(
|
||||
world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
let entity_ref = world.entity(entity);
|
||||
let Some(child_of) = entity_ref.get::<ChildOf>() else {
|
||||
@ -282,8 +283,9 @@ pub fn validate_parent_has_component<C: Component>(
|
||||
// TODO: print name here once Name lives in bevy_ecs
|
||||
let name: Option<String> = None;
|
||||
warn!(
|
||||
"warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\
|
||||
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
|
||||
This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004",
|
||||
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
|
||||
ty_name = ShortName::of::<C>(),
|
||||
name = name.map_or_else(
|
||||
|| format!("Entity {}", entity),
|
||||
|
||||
@ -2030,8 +2030,8 @@ mod tests {
|
||||
world.insert_resource(I(0));
|
||||
world
|
||||
.register_component_hooks::<Y>()
|
||||
.on_add(|mut world, _, _| world.resource_mut::<A>().0 += 1)
|
||||
.on_insert(|mut world, _, _| world.resource_mut::<I>().0 += 1);
|
||||
.on_add(|mut world, _, _, _| world.resource_mut::<A>().0 += 1)
|
||||
.on_insert(|mut world, _, _, _| world.resource_mut::<I>().0 += 1);
|
||||
|
||||
// Spawn entity and ensure Y was added
|
||||
assert!(world.spawn(X).contains::<Y>());
|
||||
@ -2060,8 +2060,8 @@ mod tests {
|
||||
world.insert_resource(I(0));
|
||||
world
|
||||
.register_component_hooks::<Y>()
|
||||
.on_add(|mut world, _, _| world.resource_mut::<A>().0 += 1)
|
||||
.on_insert(|mut world, _, _| world.resource_mut::<I>().0 += 1);
|
||||
.on_add(|mut world, _, _, _| world.resource_mut::<A>().0 += 1)
|
||||
.on_insert(|mut world, _, _, _| world.resource_mut::<I>().0 += 1);
|
||||
|
||||
// Spawn entity and ensure Y was added
|
||||
assert!(world.spawn_empty().insert(X).contains::<Y>());
|
||||
|
||||
@ -15,7 +15,7 @@ impl Component for ObservedBy {
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_remove(|mut world, entity, _| {
|
||||
hooks.on_remove(|mut world, entity, _, _| {
|
||||
let observed_by = {
|
||||
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
|
||||
core::mem::take(&mut component.0)
|
||||
|
||||
@ -24,6 +24,9 @@ use core::{
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
|
||||
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
|
||||
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
|
||||
/// contains event propagation information. See [`Trigger::propagate`] for more information.
|
||||
@ -138,6 +141,12 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
|
||||
pub fn get_propagate(&self) -> bool {
|
||||
*self.propagate
|
||||
}
|
||||
|
||||
/// Returns the source code location that triggered this observer.
|
||||
#[cfg(feature = "track_location")]
|
||||
pub fn caller(&self) -> &'static Location<'static> {
|
||||
self.trigger.caller
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E: Debug, B: Bundle> Debug for Trigger<'w, E, B> {
|
||||
@ -311,6 +320,10 @@ pub struct ObserverTrigger {
|
||||
components: SmallVec<[ComponentId; 2]>,
|
||||
/// The entity the trigger targeted.
|
||||
pub target: Entity,
|
||||
|
||||
/// The location of the source code that triggered the obserer.
|
||||
#[cfg(feature = "track_location")]
|
||||
pub caller: &'static Location<'static>,
|
||||
}
|
||||
|
||||
impl ObserverTrigger {
|
||||
@ -387,6 +400,7 @@ impl Observers {
|
||||
components: impl Iterator<Item = ComponentId> + Clone,
|
||||
data: &mut T,
|
||||
propagate: &mut bool,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
|
||||
let (mut world, observers) = unsafe {
|
||||
@ -411,6 +425,8 @@ impl Observers {
|
||||
event_type,
|
||||
components: components.clone().collect(),
|
||||
target,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
},
|
||||
data.into(),
|
||||
propagate,
|
||||
@ -532,16 +548,38 @@ impl World {
|
||||
/// While event types commonly implement [`Copy`],
|
||||
/// those that don't will be consumed and will no longer be accessible.
|
||||
/// If you need to use the event after triggering it, use [`World::trigger_ref`] instead.
|
||||
pub fn trigger<E: Event>(&mut self, mut event: E) {
|
||||
#[track_caller]
|
||||
pub fn trigger<E: Event>(&mut self, event: E) {
|
||||
self.trigger_with_caller(
|
||||
event,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn trigger_with_caller<E: Event>(
|
||||
&mut self,
|
||||
mut event: E,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, ()) };
|
||||
unsafe {
|
||||
self.trigger_targets_dynamic_ref_with_caller(
|
||||
event_id,
|
||||
&mut event,
|
||||
(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] as a mutable reference, which will run any [`Observer`]s watching for it.
|
||||
///
|
||||
/// Compared to [`World::trigger`], this method is most useful when it's necessary to check
|
||||
/// or use the event after it has been modified by observers.
|
||||
#[track_caller]
|
||||
pub fn trigger_ref<E: Event>(&mut self, event: &mut E) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
@ -553,10 +591,33 @@ impl World {
|
||||
/// While event types commonly implement [`Copy`],
|
||||
/// those that don't will be consumed and will no longer be accessible.
|
||||
/// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead.
|
||||
pub fn trigger_targets<E: Event>(&mut self, mut event: E, targets: impl TriggerTargets) {
|
||||
#[track_caller]
|
||||
pub fn trigger_targets<E: Event>(&mut self, event: E, targets: impl TriggerTargets) {
|
||||
self.trigger_targets_with_caller(
|
||||
event,
|
||||
targets,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn trigger_targets_with_caller<E: Event>(
|
||||
&mut self,
|
||||
mut event: E,
|
||||
targets: impl TriggerTargets,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
unsafe { self.trigger_targets_dynamic_ref(event_id, &mut event, targets) };
|
||||
unsafe {
|
||||
self.trigger_targets_dynamic_ref_with_caller(
|
||||
event_id,
|
||||
&mut event,
|
||||
targets,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers the given [`Event`] as a mutable reference for the given `targets`,
|
||||
@ -564,6 +625,7 @@ impl World {
|
||||
///
|
||||
/// Compared to [`World::trigger_targets`], this method is most useful when it's necessary to check
|
||||
/// or use the event after it has been modified by observers.
|
||||
#[track_caller]
|
||||
pub fn trigger_targets_ref<E: Event>(&mut self, event: &mut E, targets: impl TriggerTargets) {
|
||||
let event_id = E::register_component_id(self);
|
||||
// SAFETY: We just registered `event_id` with the type of `event`
|
||||
@ -579,6 +641,7 @@ impl World {
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_targets_dynamic<E: Event, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
@ -600,11 +663,31 @@ impl World {
|
||||
/// # Safety
|
||||
///
|
||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
||||
#[track_caller]
|
||||
pub unsafe fn trigger_targets_dynamic_ref<E: Event, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
event_data: &mut E,
|
||||
targets: Targets,
|
||||
) {
|
||||
self.trigger_targets_dynamic_ref_with_caller(
|
||||
event_id,
|
||||
event_data,
|
||||
targets,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// See `trigger_targets_dynamic_ref`
|
||||
unsafe fn trigger_targets_dynamic_ref_with_caller<E: Event, Targets: TriggerTargets>(
|
||||
&mut self,
|
||||
event_id: ComponentId,
|
||||
event_data: &mut E,
|
||||
targets: Targets,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
let mut world = DeferredWorld::from(self);
|
||||
if targets.entities().is_empty() {
|
||||
@ -616,6 +699,8 @@ impl World {
|
||||
targets.components(),
|
||||
event_data,
|
||||
false,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
};
|
||||
} else {
|
||||
@ -628,6 +713,8 @@ impl World {
|
||||
targets.components(),
|
||||
event_data,
|
||||
E::AUTO_PROPAGATE,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -756,6 +843,8 @@ impl World {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{vec, vec::Vec};
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
|
||||
use bevy_ptr::OwningPtr;
|
||||
use bevy_utils::HashMap;
|
||||
@ -1511,6 +1600,40 @@ mod tests {
|
||||
assert!(world.get_resource::<ResA>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "track_location")]
|
||||
#[track_caller]
|
||||
fn observer_caller_location_event() {
|
||||
#[derive(Event)]
|
||||
struct EventA;
|
||||
|
||||
let caller = Location::caller();
|
||||
let mut world = World::new();
|
||||
world.add_observer(move |trigger: Trigger<EventA>| {
|
||||
assert_eq!(trigger.caller(), caller);
|
||||
});
|
||||
world.trigger(EventA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "track_location")]
|
||||
#[track_caller]
|
||||
fn observer_caller_location_command_archetype_move() {
|
||||
#[derive(Component)]
|
||||
struct Component;
|
||||
|
||||
let caller = Location::caller();
|
||||
let mut world = World::new();
|
||||
world.add_observer(move |trigger: Trigger<OnAdd, Component>| {
|
||||
assert_eq!(trigger.caller(), caller);
|
||||
});
|
||||
world.add_observer(move |trigger: Trigger<OnRemove, Component>| {
|
||||
assert_eq!(trigger.caller(), caller);
|
||||
});
|
||||
world.commands().spawn(Component).clear();
|
||||
world.flush();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_triggered_components() {
|
||||
#[derive(Resource, Default)]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use alloc::{boxed::Box, vec, vec::Vec};
|
||||
use core::any::Any;
|
||||
use core::panic::Location;
|
||||
|
||||
use crate::{
|
||||
component::{ComponentHook, ComponentHooks, ComponentId, Mutable, StorageType},
|
||||
@ -66,12 +67,12 @@ impl Component for ObserverState {
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_add(|mut world, entity, _| {
|
||||
hooks.on_add(|mut world, entity, _, _| {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
world.register_observer(entity);
|
||||
});
|
||||
});
|
||||
hooks.on_remove(|mut world, entity, _| {
|
||||
hooks.on_remove(|mut world, entity, _, _| {
|
||||
let descriptor = core::mem::take(
|
||||
&mut world
|
||||
.entity_mut(entity)
|
||||
@ -318,12 +319,12 @@ impl Component for Observer {
|
||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||
type Mutability = Mutable;
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_add(|world, entity, _id| {
|
||||
hooks.on_add(|world, entity, id, caller| {
|
||||
let Some(observe) = world.get::<Self>(entity) else {
|
||||
return;
|
||||
};
|
||||
let hook = observe.hook_on_add;
|
||||
hook(world, entity, _id);
|
||||
hook(world, entity, id, caller);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -396,6 +397,7 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
mut world: DeferredWorld<'_>,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
let event_id = E::register_component_id(world);
|
||||
|
||||
@ -4,6 +4,9 @@ mod related_methods;
|
||||
mod relationship_query;
|
||||
mod relationship_source_collection;
|
||||
|
||||
use alloc::format;
|
||||
use core::panic::Location;
|
||||
|
||||
pub use related_methods::*;
|
||||
pub use relationship_query::*;
|
||||
pub use relationship_source_collection::*;
|
||||
@ -71,11 +74,17 @@ pub trait Relationship: Component + Sized {
|
||||
fn from(entity: Entity) -> Self;
|
||||
|
||||
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||
fn on_insert(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn on_insert(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
|
||||
if target_entity == entity {
|
||||
warn!(
|
||||
"The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.",
|
||||
"{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.",
|
||||
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
|
||||
core::any::type_name::<Self>(),
|
||||
core::any::type_name::<Self>()
|
||||
);
|
||||
@ -93,7 +102,8 @@ pub trait Relationship: Component + Sized {
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.",
|
||||
"{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.",
|
||||
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
|
||||
core::any::type_name::<Self>(),
|
||||
core::any::type_name::<Self>()
|
||||
);
|
||||
@ -103,7 +113,12 @@ pub trait Relationship: Component + Sized {
|
||||
|
||||
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||
// note: think of this as "on_drop"
|
||||
fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn on_replace(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
|
||||
if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) {
|
||||
if let Some(mut relationship_target) =
|
||||
@ -164,7 +179,12 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
|
||||
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||
// note: think of this as "on_drop"
|
||||
fn on_replace(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn on_replace(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
|
||||
// copying the RelationshipTarget collection
|
||||
// SAFETY: This only reads the Self component and queues Remove commands
|
||||
@ -180,7 +200,13 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
.handle_error_with(error_handler::silent()),
|
||||
);
|
||||
} else {
|
||||
warn!("Tried to despawn non-existent entity {}", source_entity);
|
||||
warn!(
|
||||
"{}Tried to despawn non-existent entity {}",
|
||||
caller
|
||||
.map(|location| format!("{location}: "))
|
||||
.unwrap_or_default(),
|
||||
source_entity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,7 +215,12 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
/// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
|
||||
/// that entity is despawned.
|
||||
// note: think of this as "on_drop"
|
||||
fn on_despawn(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn on_despawn(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
|
||||
// copying the RelationshipTarget collection
|
||||
// SAFETY: This only reads the Self component and queues despawn commands
|
||||
@ -205,7 +236,13 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||
.handle_error_with(error_handler::silent()),
|
||||
);
|
||||
} else {
|
||||
warn!("Tried to despawn non-existent entity {}", source_entity);
|
||||
warn!(
|
||||
"{}Tried to despawn non-existent entity {}",
|
||||
caller
|
||||
.map(|location| format!("{location}: "))
|
||||
.unwrap_or_default(),
|
||||
source_entity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,9 +265,16 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command<Result> {
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets.
|
||||
#[track_caller]
|
||||
pub fn trigger(event: impl Event) -> impl Command {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |world: &mut World| {
|
||||
world.trigger(event);
|
||||
world.trigger_with_caller(
|
||||
event,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,8 +283,15 @@ pub fn trigger_targets(
|
||||
event: impl Event,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
) -> impl Command {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |world: &mut World| {
|
||||
world.trigger_targets(event, targets);
|
||||
world.trigger_targets_with_caller(
|
||||
event,
|
||||
targets,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -186,12 +186,19 @@ pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand {
|
||||
/// An [`EntityCommand`] that adds a dynamic component to an entity.
|
||||
#[track_caller]
|
||||
pub fn insert_by_id<T: Send + 'static>(component_id: ComponentId, value: T) -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
// SAFETY:
|
||||
// - `component_id` safety is ensured by the caller
|
||||
// - `ptr` is valid within the `make` block
|
||||
OwningPtr::make(value, |ptr| unsafe {
|
||||
entity.insert_by_id(component_id, ptr);
|
||||
entity.insert_by_id_with_caller(
|
||||
component_id,
|
||||
ptr,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -214,39 +221,70 @@ pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl Ent
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
|
||||
#[track_caller]
|
||||
pub fn remove<T: Bundle>() -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.remove::<T>();
|
||||
entity.remove_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity,
|
||||
/// as well as the required components for each component removed.
|
||||
#[track_caller]
|
||||
pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.remove_with_requires::<T>();
|
||||
entity.remove_with_requires_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that removes a dynamic component from an entity.
|
||||
#[track_caller]
|
||||
pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.remove_by_id(component_id);
|
||||
entity.remove_by_id_with_caller(
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that removes all components from an entity.
|
||||
#[track_caller]
|
||||
pub fn clear() -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.clear();
|
||||
entity.clear_with_caller(
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that removes all components from an entity,
|
||||
/// except for those in the given [`Bundle`].
|
||||
#[track_caller]
|
||||
pub fn retain<T: Bundle>() -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.retain::<T>();
|
||||
entity.retain_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +294,7 @@ pub fn retain<T: Bundle>() -> impl EntityCommand {
|
||||
///
|
||||
/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured
|
||||
/// to despawn descendants. This results in "recursive despawn" behavior.
|
||||
#[track_caller]
|
||||
pub fn despawn() -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
@ -269,11 +308,18 @@ pub fn despawn() -> impl EntityCommand {
|
||||
|
||||
/// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer)
|
||||
/// listening for events of type `E` targeting an entity
|
||||
#[track_caller]
|
||||
pub fn observe<E: Event, B: Bundle, M>(
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
) -> impl EntityCommand {
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.observe(observer);
|
||||
entity.observe_with_caller(
|
||||
observer,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,8 @@ pub use parallel_scope::*;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use core::marker::PhantomData;
|
||||
use log::error;
|
||||
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
@ -685,7 +683,6 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
{
|
||||
#[cfg(feature = "track_location")]
|
||||
let caller = Location::caller();
|
||||
self.queue(move |world: &mut World| {
|
||||
if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller(
|
||||
@ -694,7 +691,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
caller,
|
||||
) {
|
||||
error!(
|
||||
"Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
||||
"{caller}: Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}",
|
||||
core::any::type_name::<B>(),
|
||||
invalid_entities
|
||||
);
|
||||
@ -1044,6 +1041,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
/// isn't scoped to specific targets.
|
||||
///
|
||||
/// [`Trigger`]: crate::observer::Trigger
|
||||
#[track_caller]
|
||||
pub fn trigger(&mut self, event: impl Event) {
|
||||
self.queue(command::trigger(event));
|
||||
}
|
||||
@ -1052,6 +1050,7 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
/// watches those targets.
|
||||
///
|
||||
/// [`Trigger`]: crate::observer::Trigger
|
||||
#[track_caller]
|
||||
pub fn trigger_targets(
|
||||
&mut self,
|
||||
event: impl Event,
|
||||
@ -1602,6 +1601,7 @@ impl<'a> EntityCommands<'a> {
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn remove<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Bundle,
|
||||
@ -1679,6 +1679,7 @@ impl<'a> EntityCommands<'a> {
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(remove_with_requires_system);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
|
||||
self.queue(entity_command::remove_with_requires::<T>())
|
||||
}
|
||||
@ -1688,11 +1689,13 @@ impl<'a> EntityCommands<'a> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided [`ComponentId`] does not exist in the [`World`].
|
||||
#[track_caller]
|
||||
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
|
||||
self.queue(entity_command::remove_by_id(component_id))
|
||||
}
|
||||
|
||||
/// Removes all components associated with the entity.
|
||||
#[track_caller]
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
self.queue(entity_command::clear())
|
||||
}
|
||||
@ -1865,6 +1868,7 @@ impl<'a> EntityCommands<'a> {
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn retain<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Bundle,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use core::ops::Deref;
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
|
||||
use crate::{
|
||||
archetype::Archetype,
|
||||
@ -126,9 +128,21 @@ impl<'w> DeferredWorld<'w> {
|
||||
// - ON_REPLACE is able to accept ZST events
|
||||
unsafe {
|
||||
let archetype = &*archetype;
|
||||
self.trigger_on_replace(archetype, entity, [component_id].into_iter());
|
||||
self.trigger_on_replace(
|
||||
archetype,
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
if archetype.has_replace_observer() {
|
||||
self.trigger_observers(ON_REPLACE, entity, [component_id].into_iter());
|
||||
self.trigger_observers(
|
||||
ON_REPLACE,
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,9 +169,21 @@ impl<'w> DeferredWorld<'w> {
|
||||
// - ON_REPLACE is able to accept ZST events
|
||||
unsafe {
|
||||
let archetype = &*archetype;
|
||||
self.trigger_on_insert(archetype, entity, [component_id].into_iter());
|
||||
self.trigger_on_insert(
|
||||
archetype,
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
if archetype.has_insert_observer() {
|
||||
self.trigger_observers(ON_INSERT, entity, [component_id].into_iter());
|
||||
self.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,13 +529,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
targets: impl Iterator<Item = ComponentId>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_add_hook() {
|
||||
for component_id in targets {
|
||||
// SAFETY: Caller ensures that these components exist
|
||||
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||
if let Some(hook) = hooks.on_add {
|
||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||
hook(
|
||||
DeferredWorld { world: self.world },
|
||||
entity,
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Some(caller),
|
||||
#[cfg(not(feature = "track_location"))]
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -525,13 +560,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
targets: impl Iterator<Item = ComponentId>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_insert_hook() {
|
||||
for component_id in targets {
|
||||
// SAFETY: Caller ensures that these components exist
|
||||
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||
if let Some(hook) = hooks.on_insert {
|
||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||
hook(
|
||||
DeferredWorld { world: self.world },
|
||||
entity,
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Some(caller),
|
||||
#[cfg(not(feature = "track_location"))]
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -547,13 +591,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
targets: impl Iterator<Item = ComponentId>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_replace_hook() {
|
||||
for component_id in targets {
|
||||
// SAFETY: Caller ensures that these components exist
|
||||
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||
if let Some(hook) = hooks.on_replace {
|
||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||
hook(
|
||||
DeferredWorld { world: self.world },
|
||||
entity,
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Some(caller),
|
||||
#[cfg(not(feature = "track_location"))]
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -569,13 +622,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
targets: impl Iterator<Item = ComponentId>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_remove_hook() {
|
||||
for component_id in targets {
|
||||
// SAFETY: Caller ensures that these components exist
|
||||
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||
if let Some(hook) = hooks.on_remove {
|
||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||
hook(
|
||||
DeferredWorld { world: self.world },
|
||||
entity,
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Some(caller),
|
||||
#[cfg(not(feature = "track_location"))]
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -591,13 +653,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
targets: impl Iterator<Item = ComponentId>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_despawn_hook() {
|
||||
for component_id in targets {
|
||||
// SAFETY: Caller ensures that these components exist
|
||||
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
|
||||
if let Some(hook) = hooks.on_despawn {
|
||||
hook(DeferredWorld { world: self.world }, entity, component_id);
|
||||
hook(
|
||||
DeferredWorld { world: self.world },
|
||||
entity,
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Some(caller),
|
||||
#[cfg(not(feature = "track_location"))]
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -613,6 +684,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
event: ComponentId,
|
||||
target: Entity,
|
||||
components: impl Iterator<Item = ComponentId> + Clone,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
Observers::invoke::<_>(
|
||||
self.reborrow(),
|
||||
@ -621,6 +693,8 @@ impl<'w> DeferredWorld<'w> {
|
||||
components,
|
||||
&mut (),
|
||||
&mut false,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
|
||||
@ -636,6 +710,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
components: &[ComponentId],
|
||||
data: &mut E,
|
||||
mut propagate: bool,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) where
|
||||
T: Traversal<E>,
|
||||
{
|
||||
@ -647,6 +722,8 @@ impl<'w> DeferredWorld<'w> {
|
||||
components.iter().copied(),
|
||||
data,
|
||||
&mut propagate,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if !propagate {
|
||||
break;
|
||||
|
||||
@ -1594,6 +1594,23 @@ impl<'w> EntityWorldMut<'w> {
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
component: OwningPtr<'_>,
|
||||
) -> &mut Self {
|
||||
self.insert_by_id_with_caller(
|
||||
component_id,
|
||||
component,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// See [`EntityWorldMut::insert_by_id`]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn insert_by_id_with_caller(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
component: OwningPtr<'_>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let change_tick = self.world.change_tick();
|
||||
@ -1616,6 +1633,8 @@ impl<'w> EntityWorldMut<'w> {
|
||||
self.location,
|
||||
Some(component).into_iter(),
|
||||
Some(storage_type).iter().cloned(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
@ -1665,6 +1684,8 @@ impl<'w> EntityWorldMut<'w> {
|
||||
self.location,
|
||||
iter_components,
|
||||
(*storage_types).iter().cloned(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
*self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types);
|
||||
self.world.flush();
|
||||
@ -1682,6 +1703,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
// TODO: BundleRemover?
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn take<T: Bundle>(&mut self) -> Option<T> {
|
||||
self.assert_not_despawned();
|
||||
let world = &mut self.world;
|
||||
@ -1727,6 +1749,8 @@ impl<'w> EntityWorldMut<'w> {
|
||||
old_archetype,
|
||||
entity,
|
||||
bundle_info,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1867,7 +1891,11 @@ impl<'w> EntityWorldMut<'w> {
|
||||
///
|
||||
/// # Safety
|
||||
/// - A `BundleInfo` with the corresponding `BundleId` must have been initialized.
|
||||
unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation {
|
||||
unsafe fn remove_bundle(
|
||||
&mut self,
|
||||
bundle: BundleId,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> EntityLocation {
|
||||
let entity = self.entity;
|
||||
let world = &mut self.world;
|
||||
let location = self.location;
|
||||
@ -1910,6 +1938,8 @@ impl<'w> EntityWorldMut<'w> {
|
||||
old_archetype,
|
||||
entity,
|
||||
bundle_info,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1956,14 +1986,32 @@ impl<'w> EntityWorldMut<'w> {
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
// TODO: BundleRemover?
|
||||
#[track_caller]
|
||||
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
|
||||
self.remove_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn remove_with_caller<T: Bundle>(
|
||||
&mut self,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let storages = &mut self.world.storages;
|
||||
let components = &mut self.world.components;
|
||||
let bundle_info = self.world.bundles.register_info::<T>(components, storages);
|
||||
|
||||
// SAFETY: the `BundleInfo` is initialized above
|
||||
self.location = unsafe { self.remove_bundle(bundle_info) };
|
||||
self.location = unsafe {
|
||||
self.remove_bundle(
|
||||
bundle_info,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -1974,7 +2022,18 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
|
||||
self.remove_with_requires_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn remove_with_requires_with_caller<T: Bundle>(
|
||||
&mut self,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let storages = &mut self.world.storages;
|
||||
let components = &mut self.world.components;
|
||||
@ -1983,7 +2042,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
let bundle_id = bundles.register_contributed_bundle_info::<T>(components, storages);
|
||||
|
||||
// SAFETY: the dynamic `BundleInfo` is initialized above
|
||||
self.location = unsafe { self.remove_bundle(bundle_id) };
|
||||
self.location = unsafe {
|
||||
self.remove_bundle(
|
||||
bundle_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -1996,7 +2061,19 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn retain<T: Bundle>(&mut self) -> &mut Self {
|
||||
self.retain_with_caller::<T>(
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn retain_with_caller<T: Bundle>(
|
||||
&mut self,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let archetypes = &mut self.world.archetypes;
|
||||
let storages = &mut self.world.storages;
|
||||
@ -2016,7 +2093,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove);
|
||||
|
||||
// SAFETY: the `BundleInfo` for the components to remove is initialized above
|
||||
self.location = unsafe { self.remove_bundle(remove_bundle) };
|
||||
self.location = unsafe {
|
||||
self.remove_bundle(
|
||||
remove_bundle,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -2030,7 +2113,21 @@ impl<'w> EntityWorldMut<'w> {
|
||||
///
|
||||
/// Panics if the provided [`ComponentId`] does not exist in the [`World`] or if the
|
||||
/// entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
|
||||
self.remove_by_id_with_caller(
|
||||
component_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn remove_by_id_with_caller(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let components = &mut self.world.components;
|
||||
|
||||
@ -2040,7 +2137,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
.init_component_info(components, component_id);
|
||||
|
||||
// SAFETY: the `BundleInfo` for this `component_id` is initialized above
|
||||
self.location = unsafe { self.remove_bundle(bundle_id) };
|
||||
self.location = unsafe {
|
||||
self.remove_bundle(
|
||||
bundle_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -2054,6 +2157,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
///
|
||||
/// Panics if any of the provided [`ComponentId`]s do not exist in the [`World`] or if the
|
||||
/// entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let components = &mut self.world.components;
|
||||
@ -2064,7 +2168,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
.init_dynamic_info(components, component_ids);
|
||||
|
||||
// SAFETY: the `BundleInfo` for this `bundle_id` is initialized above
|
||||
unsafe { self.remove_bundle(bundle_id) };
|
||||
unsafe {
|
||||
self.remove_bundle(
|
||||
bundle_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
};
|
||||
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
@ -2076,7 +2186,19 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
self.clear_with_caller(
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clear_with_caller(
|
||||
&mut self,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
let component_ids: Vec<ComponentId> = self.archetype().components().collect();
|
||||
let components = &mut self.world.components;
|
||||
@ -2087,7 +2209,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
.init_dynamic_info(components, component_ids.as_slice());
|
||||
|
||||
// SAFETY: the `BundleInfo` for this `component_id` is initialized above
|
||||
self.location = unsafe { self.remove_bundle(bundle_id) };
|
||||
self.location = unsafe {
|
||||
self.remove_bundle(
|
||||
bundle_id,
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
)
|
||||
};
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -2140,17 +2268,53 @@ impl<'w> EntityWorldMut<'w> {
|
||||
// SAFETY: All components in the archetype exist in world
|
||||
unsafe {
|
||||
if archetype.has_despawn_observer() {
|
||||
deferred_world.trigger_observers(ON_DESPAWN, self.entity, archetype.components());
|
||||
deferred_world.trigger_observers(
|
||||
ON_DESPAWN,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_despawn(archetype, self.entity, archetype.components());
|
||||
deferred_world.trigger_on_despawn(
|
||||
archetype,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if archetype.has_replace_observer() {
|
||||
deferred_world.trigger_observers(ON_REPLACE, self.entity, archetype.components());
|
||||
deferred_world.trigger_observers(
|
||||
ON_REPLACE,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_replace(archetype, self.entity, archetype.components());
|
||||
deferred_world.trigger_on_replace(
|
||||
archetype,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
if archetype.has_remove_observer() {
|
||||
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components());
|
||||
deferred_world.trigger_observers(
|
||||
ON_REMOVE,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components());
|
||||
deferred_world.trigger_on_remove(
|
||||
archetype,
|
||||
self.entity,
|
||||
archetype.components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
);
|
||||
}
|
||||
|
||||
for component_id in archetype.components() {
|
||||
@ -2387,13 +2551,29 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
#[track_caller]
|
||||
pub fn observe<E: Event, B: Bundle, M>(
|
||||
&mut self,
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
) -> &mut Self {
|
||||
self.observe_with_caller(
|
||||
observer,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn observe_with_caller<E: Event, B: Bundle, M>(
|
||||
&mut self,
|
||||
observer: impl IntoObserverSystem<E, B, M>,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
self.world
|
||||
.spawn(Observer::new(observer).with_entity(self.entity));
|
||||
self.world.spawn_with_caller(
|
||||
Observer::new(observer).with_entity(self.entity),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
self.world.flush();
|
||||
self.update_location();
|
||||
self
|
||||
@ -2571,19 +2751,40 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||
archetype: &Archetype,
|
||||
entity: Entity,
|
||||
bundle_info: &BundleInfo,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) {
|
||||
if archetype.has_replace_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_REPLACE,
|
||||
entity,
|
||||
bundle_info.iter_explicit_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_replace(archetype, entity, bundle_info.iter_explicit_components());
|
||||
deferred_world.trigger_on_replace(
|
||||
archetype,
|
||||
entity,
|
||||
bundle_info.iter_explicit_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
if archetype.has_remove_observer() {
|
||||
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_explicit_components());
|
||||
deferred_world.trigger_observers(
|
||||
ON_REMOVE,
|
||||
entity,
|
||||
bundle_info.iter_explicit_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_explicit_components());
|
||||
deferred_world.trigger_on_remove(
|
||||
archetype,
|
||||
entity,
|
||||
bundle_info.iter_explicit_components(),
|
||||
#[cfg(feature = "track_location")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
|
||||
/// A view into a single entity and component in a world, which may either be vacant or occupied.
|
||||
@ -3860,7 +4061,6 @@ where
|
||||
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
||||
/// [`BundleInfo`] used to construct [`BundleInserter`]
|
||||
/// - [`Entity`] must correspond to [`EntityLocation`]
|
||||
#[track_caller]
|
||||
unsafe fn insert_dynamic_bundle<
|
||||
'a,
|
||||
I: Iterator<Item = OwningPtr<'a>>,
|
||||
@ -3871,6 +4071,7 @@ unsafe fn insert_dynamic_bundle<
|
||||
location: EntityLocation,
|
||||
components: I,
|
||||
storage_types: S,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> EntityLocation {
|
||||
struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
|
||||
components: I,
|
||||
@ -3896,7 +4097,7 @@ unsafe fn insert_dynamic_bundle<
|
||||
bundle,
|
||||
InsertMode::Replace,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
caller,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -4200,7 +4401,6 @@ mod tests {
|
||||
use bevy_ptr::{OwningPtr, Ptr};
|
||||
use core::panic::AssertUnwindSafe;
|
||||
|
||||
#[cfg(feature = "track_location")]
|
||||
use core::panic::Location;
|
||||
#[cfg(feature = "track_location")]
|
||||
use std::sync::OnceLock;
|
||||
@ -5282,12 +5482,22 @@ mod tests {
|
||||
#[component(on_add = ord_a_hook_on_add, on_insert = ord_a_hook_on_insert, on_replace = ord_a_hook_on_replace, on_remove = ord_a_hook_on_remove)]
|
||||
struct OrdA;
|
||||
|
||||
fn ord_a_hook_on_add(mut world: DeferredWorld, entity: Entity, _id: ComponentId) {
|
||||
fn ord_a_hook_on_add(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<TestVec>().0.push("OrdA hook on_add");
|
||||
world.commands().entity(entity).insert(OrdB);
|
||||
}
|
||||
|
||||
fn ord_a_hook_on_insert(mut world: DeferredWorld, entity: Entity, _id: ComponentId) {
|
||||
fn ord_a_hook_on_insert(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
@ -5296,14 +5506,24 @@ mod tests {
|
||||
world.commands().entity(entity).remove::<OrdB>();
|
||||
}
|
||||
|
||||
fn ord_a_hook_on_replace(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_a_hook_on_replace(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
.push("OrdA hook on_replace");
|
||||
}
|
||||
|
||||
fn ord_a_hook_on_remove(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_a_hook_on_remove(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
@ -5330,7 +5550,12 @@ mod tests {
|
||||
#[component(on_add = ord_b_hook_on_add, on_insert = ord_b_hook_on_insert, on_replace = ord_b_hook_on_replace, on_remove = ord_b_hook_on_remove)]
|
||||
struct OrdB;
|
||||
|
||||
fn ord_b_hook_on_add(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_b_hook_on_add(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world.resource_mut::<TestVec>().0.push("OrdB hook on_add");
|
||||
world.commands().queue(|world: &mut World| {
|
||||
world
|
||||
@ -5340,21 +5565,36 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
fn ord_b_hook_on_insert(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_b_hook_on_insert(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
.push("OrdB hook on_insert");
|
||||
}
|
||||
|
||||
fn ord_b_hook_on_replace(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_b_hook_on_replace(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
.push("OrdB hook on_replace");
|
||||
}
|
||||
|
||||
fn ord_b_hook_on_remove(mut world: DeferredWorld, _entity: Entity, _id: ComponentId) {
|
||||
fn ord_b_hook_on_remove(
|
||||
mut world: DeferredWorld,
|
||||
_entity: Entity,
|
||||
_id: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
world
|
||||
.resource_mut::<TestVec>()
|
||||
.0
|
||||
@ -5494,7 +5734,7 @@ mod tests {
|
||||
struct C;
|
||||
|
||||
static TRACKED: OnceLock<&'static Location<'static>> = OnceLock::new();
|
||||
fn get_tracked(world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn get_tracked(world: DeferredWorld, entity: Entity, _: ComponentId, _: Option<&Location>) {
|
||||
TRACKED.get_or_init(|| {
|
||||
world
|
||||
.entities
|
||||
@ -5555,7 +5795,7 @@ mod tests {
|
||||
world.register_component::<Foo>();
|
||||
world
|
||||
.register_component_hooks::<Foo>()
|
||||
.on_add(|world, entity, _| {
|
||||
.on_add(|world, entity, _, _| {
|
||||
ADD_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
assert_eq!(
|
||||
@ -5563,7 +5803,7 @@ mod tests {
|
||||
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
|
||||
);
|
||||
})
|
||||
.on_remove(|world, entity, _| {
|
||||
.on_remove(|world, entity, _, _| {
|
||||
REMOVE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
assert_eq!(
|
||||
@ -5571,7 +5811,7 @@ mod tests {
|
||||
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
|
||||
);
|
||||
})
|
||||
.on_replace(|world, entity, _| {
|
||||
.on_replace(|world, entity, _, _| {
|
||||
REPLACE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
assert_eq!(
|
||||
@ -5579,7 +5819,7 @@ mod tests {
|
||||
Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed)))
|
||||
);
|
||||
})
|
||||
.on_insert(|world, entity, _| {
|
||||
.on_insert(|world, entity, _, _| {
|
||||
INSERT_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@ -1077,6 +1077,18 @@ impl World {
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
|
||||
self.spawn_with_caller(
|
||||
bundle,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_with_caller<B: Bundle>(
|
||||
&mut self,
|
||||
bundle: B,
|
||||
#[cfg(feature = "track_location")] caller: &'static Location<'static>,
|
||||
) -> EntityWorldMut {
|
||||
self.flush();
|
||||
let change_tick = self.change_tick();
|
||||
let entity = self.entities.alloc();
|
||||
@ -1087,7 +1099,7 @@ impl World {
|
||||
entity,
|
||||
bundle,
|
||||
#[cfg(feature = "track_location")]
|
||||
Location::caller(),
|
||||
caller,
|
||||
)
|
||||
};
|
||||
|
||||
@ -1102,7 +1114,7 @@ impl World {
|
||||
|
||||
#[cfg(feature = "track_location")]
|
||||
self.entities
|
||||
.set_spawned_or_despawned_by(entity.index(), Location::caller());
|
||||
.set_spawned_or_despawned_by(entity.index(), caller);
|
||||
|
||||
// SAFETY: entity and location are valid, as they were just created above
|
||||
unsafe { EntityWorldMut::new(self, entity, entity_location) }
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
//! Contains the [`AutoFocus`] component and related machinery.
|
||||
|
||||
use core::panic::Location;
|
||||
|
||||
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
|
||||
|
||||
use crate::InputFocus;
|
||||
@ -23,7 +25,12 @@ use bevy_reflect::{prelude::*, Reflect};
|
||||
#[component(on_add = on_auto_focus_added)]
|
||||
pub struct AutoFocus;
|
||||
|
||||
fn on_auto_focus_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn on_auto_focus_added(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&'static Location<'static>>,
|
||||
) {
|
||||
if let Some(mut input_focus) = world.get_resource_mut::<InputFocus>() {
|
||||
input_focus.set(entity);
|
||||
}
|
||||
|
||||
@ -368,13 +368,19 @@ mod tests {
|
||||
ButtonState, InputPlugin,
|
||||
};
|
||||
use bevy_window::WindowResolution;
|
||||
use core::panic::Location;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(on_add = set_focus_on_add)]
|
||||
struct SetFocusOnAdd;
|
||||
|
||||
fn set_focus_on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn set_focus_on_add(
|
||||
mut world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&Location>,
|
||||
) {
|
||||
let mut input_focus = world.resource_mut::<InputFocus>();
|
||||
input_focus.set(entity);
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
//! you can use the [`TabNavigation`] system parameter directly instead.
|
||||
//! This object can be injected into your systems, and provides a [`navigate`](`TabNavigation::navigate`) method which can be
|
||||
//! used to navigate between focusable entities.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use bevy_app::{App, Plugin, Startup};
|
||||
use bevy_ecs::{
|
||||
|
||||
@ -43,6 +43,7 @@ use bevy_window::{
|
||||
WindowScaleFactorChanged,
|
||||
};
|
||||
use core::ops::Range;
|
||||
use core::panic::Location;
|
||||
use derive_more::derive::From;
|
||||
use tracing::warn;
|
||||
use wgpu::{BlendState, TextureFormat, TextureUsages};
|
||||
@ -332,9 +333,14 @@ pub struct Camera {
|
||||
pub sub_camera_view: Option<SubCameraView>,
|
||||
}
|
||||
|
||||
fn warn_on_no_render_graph(world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
fn warn_on_no_render_graph(
|
||||
world: DeferredWorld,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
if !world.entity(entity).contains::<CameraRenderGraph>() {
|
||||
warn!("Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.");
|
||||
warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ impl<C: Component> Plugin for SyncComponentPlugin<C> {
|
||||
app.register_required_components::<C, SyncToRenderWorld>();
|
||||
|
||||
app.world_mut().register_component_hooks::<C>().on_remove(
|
||||
|mut world, entity, _component_id| {
|
||||
|mut world, entity, _component_id, _caller| {
|
||||
let mut pending = world.resource_mut::<PendingSyncEntity>();
|
||||
pending.push(EntityRecord::ComponentRemoved(entity));
|
||||
},
|
||||
|
||||
@ -2,6 +2,7 @@ mod range;
|
||||
mod render_layers;
|
||||
|
||||
use core::any::TypeId;
|
||||
use core::panic::Location;
|
||||
|
||||
use bevy_ecs::component::ComponentId;
|
||||
use bevy_ecs::entity::hash_set::EntityHashSet;
|
||||
@ -632,8 +633,12 @@ pub fn check_visibility(
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn add_visibility_class<C>(mut world: DeferredWorld<'_>, entity: Entity, _: ComponentId)
|
||||
where
|
||||
pub fn add_visibility_class<C>(
|
||||
mut world: DeferredWorld<'_>,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
_: Option<&Location>,
|
||||
) where
|
||||
C: 'static,
|
||||
{
|
||||
if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
|
||||
|
||||
@ -365,7 +365,7 @@ mod tests {
|
||||
let mut dst_world = World::new();
|
||||
dst_world
|
||||
.register_component_hooks::<A>()
|
||||
.on_add(|mut world, _, _| {
|
||||
.on_add(|mut world, _, _, _| {
|
||||
world.commands().spawn_empty();
|
||||
});
|
||||
dst_world.insert_resource(reg.clone());
|
||||
|
||||
@ -68,7 +68,7 @@ impl Plugin for ScenePlugin {
|
||||
// Register component hooks for DynamicSceneRoot
|
||||
app.world_mut()
|
||||
.register_component_hooks::<DynamicSceneRoot>()
|
||||
.on_remove(|mut world, entity, _| {
|
||||
.on_remove(|mut world, entity, _, _| {
|
||||
let Some(handle) = world.get::<DynamicSceneRoot>(entity) else {
|
||||
return;
|
||||
};
|
||||
@ -87,7 +87,7 @@ impl Plugin for ScenePlugin {
|
||||
// Register component hooks for SceneRoot
|
||||
app.world_mut()
|
||||
.register_component_hooks::<SceneRoot>()
|
||||
.on_remove(|mut world, entity, _| {
|
||||
.on_remove(|mut world, entity, _, _| {
|
||||
if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) {
|
||||
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
|
||||
return;
|
||||
|
||||
@ -63,16 +63,22 @@ fn setup(world: &mut World) {
|
||||
world
|
||||
.register_component_hooks::<MyComponent>()
|
||||
// There are 4 component lifecycle hooks: `on_add`, `on_insert`, `on_replace` and `on_remove`
|
||||
// A hook has 3 arguments:
|
||||
// A hook has 4 arguments:
|
||||
// - a `DeferredWorld`, this allows access to resource and component data as well as `Commands`
|
||||
// - the entity that triggered the hook
|
||||
// - the component id of the triggering component, this is mostly used for dynamic components
|
||||
// - the location of the code that caused the hook to trigger
|
||||
//
|
||||
// `on_add` will trigger when a component is inserted onto an entity without it
|
||||
.on_add(|mut world, entity, component_id| {
|
||||
.on_add(|mut world, entity, component_id, caller| {
|
||||
// You can access component data from within the hook
|
||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||
println!("Component: {component_id:?} added to: {entity} with value {value:?}");
|
||||
println!(
|
||||
"{component_id:?} added to {entity} with value {value:?}{}",
|
||||
caller
|
||||
.map(|location| format!("due to {location}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
// Or access resources
|
||||
world
|
||||
.resource_mut::<MyComponentIndex>()
|
||||
@ -82,21 +88,26 @@ fn setup(world: &mut World) {
|
||||
})
|
||||
// `on_insert` will trigger when a component is inserted onto an entity,
|
||||
// regardless of whether or not it already had it and after `on_add` if it ran
|
||||
.on_insert(|world, _, _| {
|
||||
.on_insert(|world, _, _, _| {
|
||||
println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
|
||||
})
|
||||
// `on_replace` will trigger when a component is inserted onto an entity that already had it,
|
||||
// and runs before the value is replaced.
|
||||
// Also triggers when a component is removed from an entity, and runs before `on_remove`
|
||||
.on_replace(|mut world, entity, _| {
|
||||
.on_replace(|mut world, entity, _, _| {
|
||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||
world.resource_mut::<MyComponentIndex>().remove(&value);
|
||||
})
|
||||
// `on_remove` will trigger when a component is removed from an entity,
|
||||
// since it runs before the component is removed you can still access the component data
|
||||
.on_remove(|mut world, entity, component_id| {
|
||||
.on_remove(|mut world, entity, component_id, caller| {
|
||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||
println!("Component: {component_id:?} removed from: {entity} with value {value:?}");
|
||||
println!(
|
||||
"{component_id:?} removed from {entity} with value {value:?}{}",
|
||||
caller
|
||||
.map(|location| format!("due to {location}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
// You can also issue commands through `.commands()`
|
||||
world.commands().entity(entity).despawn();
|
||||
});
|
||||
|
||||
@ -10,6 +10,7 @@ use bevy::{
|
||||
utils::HashMap,
|
||||
};
|
||||
use core::alloc::Layout;
|
||||
use core::panic::Location;
|
||||
|
||||
/// This component is mutable, the default case. This is indicated by components
|
||||
/// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
|
||||
@ -73,7 +74,12 @@ impl NameIndex {
|
||||
///
|
||||
/// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
|
||||
/// inserted in the index, and its value will not change without triggering a hook.
|
||||
fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) {
|
||||
fn on_insert_name(
|
||||
mut world: DeferredWorld<'_>,
|
||||
entity: Entity,
|
||||
_component: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
let Some(&name) = world.entity(entity).get::<Name>() else {
|
||||
unreachable!("OnInsert hook guarantees `Name` is available on entity")
|
||||
};
|
||||
@ -88,7 +94,12 @@ fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: Comp
|
||||
///
|
||||
/// Since all mutations to [`Name`] are captured by hooks, we know it is currently
|
||||
/// inserted in the index.
|
||||
fn on_replace_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) {
|
||||
fn on_replace_name(
|
||||
mut world: DeferredWorld<'_>,
|
||||
entity: Entity,
|
||||
_component: ComponentId,
|
||||
_caller: Option<&'static Location<'static>>,
|
||||
) {
|
||||
let Some(&name) = world.entity(entity).get::<Name>() else {
|
||||
unreachable!("OnReplace hook guarantees `Name` is available on entity")
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user