Skip to content

Commit eb6afd2

Browse files
JaySprucechescock
andauthored
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>
1 parent b0df3fb commit eb6afd2

File tree

3 files changed

+69
-8
lines changed

3 files changed

+69
-8
lines changed

crates/bevy_ecs/src/relationship/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub trait Relationship: Component + Sized {
133133
.and_modify(move |mut relationship_target| {
134134
relationship_target.collection_mut_risky().add(entity);
135135
})
136-
.or_insert_with(|| {
136+
.or_insert_with(move || {
137137
let mut target = Self::RelationshipTarget::with_capacity(1);
138138
target.collection_mut_risky().add(entity);
139139
target

crates/bevy_ecs/src/system/commands/entity_command.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,38 @@ pub unsafe fn insert_by_id<T: Send + 'static>(
143143

144144
/// An [`EntityCommand`] that adds a component to an entity using
145145
/// the component's [`FromWorld`] implementation.
146+
///
147+
/// `T::from_world` will only be invoked if the component will actually be inserted.
148+
/// In other words, `T::from_world` will *not* be invoked if `mode` is [`InsertMode::Keep`]
149+
/// and the entity already has the component.
146150
#[track_caller]
147151
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
148152
let caller = MaybeLocation::caller();
149153
move |mut entity: EntityWorldMut| {
150-
let value = entity.world_scope(|world| T::from_world(world));
151-
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
154+
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
155+
let value = entity.world_scope(|world| T::from_world(world));
156+
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
157+
}
158+
}
159+
}
160+
161+
/// An [`EntityCommand`] that adds a component to an entity using
162+
/// some function that returns the component.
163+
///
164+
/// The function will only be invoked if the component will actually be inserted.
165+
/// In other words, the function will *not* be invoked if `mode` is [`InsertMode::Keep`]
166+
/// and the entity already has the component.
167+
#[track_caller]
168+
pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
169+
where
170+
F: FnOnce() -> T + Send + 'static,
171+
{
172+
let caller = MaybeLocation::caller();
173+
move |mut entity: EntityWorldMut| {
174+
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
175+
let value = component_fn();
176+
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
177+
}
152178
}
153179
}
154180

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,35 +2273,53 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
22732273

22742274
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
22752275
/// if `T` is not already present.
2276+
///
2277+
/// `default` will only be invoked if the component will actually be inserted.
22762278
#[track_caller]
2277-
pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
2278-
self.or_insert(default())
2279+
pub fn or_insert_with<F>(&mut self, default: F) -> &mut Self
2280+
where
2281+
F: FnOnce() -> T + Send + 'static,
2282+
{
2283+
self.entity_commands
2284+
.queue(entity_command::insert_with(default, InsertMode::Keep));
2285+
self
22792286
}
22802287

22812288
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
22822289
/// if `T` is not already present.
22832290
///
2291+
/// `default` will only be invoked if the component will actually be inserted.
2292+
///
22842293
/// # Note
22852294
///
22862295
/// If the entity does not exist when this command is executed,
22872296
/// the resulting error will be ignored.
22882297
#[track_caller]
2289-
pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
2290-
self.or_try_insert(default())
2298+
pub fn or_try_insert_with<F>(&mut self, default: F) -> &mut Self
2299+
where
2300+
F: FnOnce() -> T + Send + 'static,
2301+
{
2302+
self.entity_commands
2303+
.queue_silenced(entity_command::insert_with(default, InsertMode::Keep));
2304+
self
22912305
}
22922306

22932307
/// [Insert](EntityCommands::insert) `T::default` into this entity,
22942308
/// if `T` is not already present.
2309+
///
2310+
/// `T::default` will only be invoked if the component will actually be inserted.
22952311
#[track_caller]
22962312
pub fn or_default(&mut self) -> &mut Self
22972313
where
22982314
T: Default,
22992315
{
2300-
self.or_insert(T::default())
2316+
self.or_insert_with(T::default)
23012317
}
23022318

23032319
/// [Insert](EntityCommands::insert) `T::from_world` into this entity,
23042320
/// if `T` is not already present.
2321+
///
2322+
/// `T::from_world` will only be invoked if the component will actually be inserted.
23052323
#[track_caller]
23062324
pub fn or_from_world(&mut self) -> &mut Self
23072325
where
@@ -2396,6 +2414,12 @@ mod tests {
23962414
}
23972415
}
23982416

2417+
impl Default for W<u8> {
2418+
fn default() -> Self {
2419+
unreachable!()
2420+
}
2421+
}
2422+
23992423
#[test]
24002424
fn entity_commands_entry() {
24012425
let mut world = World::default();
@@ -2435,6 +2459,17 @@ mod tests {
24352459
let id = commands.entity(entity).entry::<W<u64>>().entity().id();
24362460
queue.apply(&mut world);
24372461
assert_eq!(id, entity);
2462+
let mut commands = Commands::new(&mut queue, &world);
2463+
commands
2464+
.entity(entity)
2465+
.entry::<W<u8>>()
2466+
.or_insert_with(|| W(5))
2467+
.or_insert_with(|| unreachable!())
2468+
.or_try_insert_with(|| unreachable!())
2469+
.or_default()
2470+
.or_from_world();
2471+
queue.apply(&mut world);
2472+
assert_eq!(5, world.get::<W<u8>>(entity).unwrap().0);
24382473
}
24392474

24402475
#[test]

0 commit comments

Comments
 (0)