Avoid early function invocation in EntityEntryCommands
(#19978)
## Objective Fixes #19884. ## Solution - Add an internal entity command `insert_with`, which takes a function returning a component and checks if the component would actually be inserted before invoking the function. - Add the same check to `insert_from_world`, since it's a similar situation. - Update the `or_insert_with`, `or_try_insert_with`, and `or_default` methods on `EntityEntryCommands` to use the new command. Since the function/closure returning the component now needs to be sent into the command (rather than being invoked before the command is created), the function now has `Send + 'static` bounds. Pretty typical for command stuff, but I don't know how/if it'll affect existing users. --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
This commit is contained in:
parent
b0df3fb4d0
commit
eb6afd26a1
@ -133,7 +133,7 @@ pub trait Relationship: Component + Sized {
|
|||||||
.and_modify(move |mut relationship_target| {
|
.and_modify(move |mut relationship_target| {
|
||||||
relationship_target.collection_mut_risky().add(entity);
|
relationship_target.collection_mut_risky().add(entity);
|
||||||
})
|
})
|
||||||
.or_insert_with(|| {
|
.or_insert_with(move || {
|
||||||
let mut target = Self::RelationshipTarget::with_capacity(1);
|
let mut target = Self::RelationshipTarget::with_capacity(1);
|
||||||
target.collection_mut_risky().add(entity);
|
target.collection_mut_risky().add(entity);
|
||||||
target
|
target
|
||||||
|
@ -143,14 +143,40 @@ pub unsafe fn insert_by_id<T: Send + 'static>(
|
|||||||
|
|
||||||
/// An [`EntityCommand`] that adds a component to an entity using
|
/// An [`EntityCommand`] that adds a component to an entity using
|
||||||
/// the component's [`FromWorld`] implementation.
|
/// the component's [`FromWorld`] implementation.
|
||||||
|
///
|
||||||
|
/// `T::from_world` will only be invoked if the component will actually be inserted.
|
||||||
|
/// In other words, `T::from_world` will *not* be invoked if `mode` is [`InsertMode::Keep`]
|
||||||
|
/// and the entity already has the component.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
|
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
|
||||||
let caller = MaybeLocation::caller();
|
let caller = MaybeLocation::caller();
|
||||||
move |mut entity: EntityWorldMut| {
|
move |mut entity: EntityWorldMut| {
|
||||||
|
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
|
||||||
let value = entity.world_scope(|world| T::from_world(world));
|
let value = entity.world_scope(|world| T::from_world(world));
|
||||||
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [`EntityCommand`] that adds a component to an entity using
|
||||||
|
/// some function that returns the component.
|
||||||
|
///
|
||||||
|
/// The function will only be invoked if the component will actually be inserted.
|
||||||
|
/// In other words, the function will *not* be invoked if `mode` is [`InsertMode::Keep`]
|
||||||
|
/// and the entity already has the component.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
|
||||||
|
where
|
||||||
|
F: FnOnce() -> T + Send + 'static,
|
||||||
|
{
|
||||||
|
let caller = MaybeLocation::caller();
|
||||||
|
move |mut entity: EntityWorldMut| {
|
||||||
|
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
|
||||||
|
let value = component_fn();
|
||||||
|
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
|
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -2273,35 +2273,53 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
|
|||||||
|
|
||||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
||||||
/// if `T` is not already present.
|
/// if `T` is not already present.
|
||||||
|
///
|
||||||
|
/// `default` will only be invoked if the component will actually be inserted.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
|
pub fn or_insert_with<F>(&mut self, default: F) -> &mut Self
|
||||||
self.or_insert(default())
|
where
|
||||||
|
F: FnOnce() -> T + Send + 'static,
|
||||||
|
{
|
||||||
|
self.entity_commands
|
||||||
|
.queue(entity_command::insert_with(default, InsertMode::Keep));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
||||||
/// if `T` is not already present.
|
/// if `T` is not already present.
|
||||||
///
|
///
|
||||||
|
/// `default` will only be invoked if the component will actually be inserted.
|
||||||
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// If the entity does not exist when this command is executed,
|
/// If the entity does not exist when this command is executed,
|
||||||
/// the resulting error will be ignored.
|
/// the resulting error will be ignored.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
|
pub fn or_try_insert_with<F>(&mut self, default: F) -> &mut Self
|
||||||
self.or_try_insert(default())
|
where
|
||||||
|
F: FnOnce() -> T + Send + 'static,
|
||||||
|
{
|
||||||
|
self.entity_commands
|
||||||
|
.queue_silenced(entity_command::insert_with(default, InsertMode::Keep));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Insert](EntityCommands::insert) `T::default` into this entity,
|
/// [Insert](EntityCommands::insert) `T::default` into this entity,
|
||||||
/// if `T` is not already present.
|
/// if `T` is not already present.
|
||||||
|
///
|
||||||
|
/// `T::default` will only be invoked if the component will actually be inserted.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn or_default(&mut self) -> &mut Self
|
pub fn or_default(&mut self) -> &mut Self
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default,
|
||||||
{
|
{
|
||||||
self.or_insert(T::default())
|
self.or_insert_with(T::default)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Insert](EntityCommands::insert) `T::from_world` into this entity,
|
/// [Insert](EntityCommands::insert) `T::from_world` into this entity,
|
||||||
/// if `T` is not already present.
|
/// if `T` is not already present.
|
||||||
|
///
|
||||||
|
/// `T::from_world` will only be invoked if the component will actually be inserted.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn or_from_world(&mut self) -> &mut Self
|
pub fn or_from_world(&mut self) -> &mut Self
|
||||||
where
|
where
|
||||||
@ -2396,6 +2414,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for W<u8> {
|
||||||
|
fn default() -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn entity_commands_entry() {
|
fn entity_commands_entry() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -2435,6 +2459,17 @@ mod tests {
|
|||||||
let id = commands.entity(entity).entry::<W<u64>>().entity().id();
|
let id = commands.entity(entity).entry::<W<u64>>().entity().id();
|
||||||
queue.apply(&mut world);
|
queue.apply(&mut world);
|
||||||
assert_eq!(id, entity);
|
assert_eq!(id, entity);
|
||||||
|
let mut commands = Commands::new(&mut queue, &world);
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.entry::<W<u8>>()
|
||||||
|
.or_insert_with(|| W(5))
|
||||||
|
.or_insert_with(|| unreachable!())
|
||||||
|
.or_try_insert_with(|| unreachable!())
|
||||||
|
.or_default()
|
||||||
|
.or_from_world();
|
||||||
|
queue.apply(&mut world);
|
||||||
|
assert_eq!(5, world.get::<W<u8>>(entity).unwrap().0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user