Skip to content

Commit b2529bf

Browse files
jpetkaualice-i-cecileSkiFire13
authored
feat: add insert_if_new (#14397) (#14646)
# Objective Often there are reasons to insert some components (e.g. Transform) separately from the rest of a bundle (e.g. PbrBundle). However `insert` overwrites existing components, making this difficult. See also issue #14397 Fixes #2054. ## Solution This PR adds the method `insert_if_new` to EntityMut and Commands, which is the same as `insert` except that the old component is kept in case of conflicts. It also renames some internal enums (from `ComponentStatus::Mutated` to `Existing`), to reflect the possible change in meaning. ## Testing *Did you test these changes? If so, how?* Added basic unit tests; used the new behavior in my project. *Are there any parts that need more testing?* There should be a test that the change time isn't set if a component is not overwritten; I wasn't sure how to write a test for that case. *How can other people (reviewers) test your changes? Is there anything specific they need to know?* `cargo test` in the bevy_ecs project. *If relevant, what platforms did you test these changes on, and are there any important ones you can't test?* Only tested on Windows, but it doesn't touch anything platform-specific. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Giacomo Stevanato <[email protected]>
1 parent a2fc9de commit b2529bf

File tree

7 files changed

+154
-28
lines changed

7 files changed

+154
-28
lines changed

crates/bevy_ecs/src/archetype.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,12 @@ impl ArchetypeId {
109109
}
110110
}
111111

112+
/// Used in [`AddBundle`] to track whether components in the bundle are newly
113+
/// added or already existed in the entity's archetype.
112114
#[derive(Copy, Clone, Eq, PartialEq)]
113115
pub(crate) enum ComponentStatus {
114116
Added,
115-
Mutated,
117+
Existing,
116118
}
117119

118120
pub(crate) struct AddBundle {
@@ -122,7 +124,7 @@ pub(crate) struct AddBundle {
122124
/// indicate if the component is newly added to the target archetype or if it already existed
123125
pub bundle_status: Vec<ComponentStatus>,
124126
pub added: Vec<ComponentId>,
125-
pub mutated: Vec<ComponentId>,
127+
pub existing: Vec<ComponentId>,
126128
}
127129

128130
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
@@ -207,15 +209,15 @@ impl Edges {
207209
archetype_id: ArchetypeId,
208210
bundle_status: Vec<ComponentStatus>,
209211
added: Vec<ComponentId>,
210-
mutated: Vec<ComponentId>,
212+
existing: Vec<ComponentId>,
211213
) {
212214
self.add_bundle.insert(
213215
bundle_id,
214216
AddBundle {
215217
archetype_id,
216218
bundle_status,
217219
added,
218-
mutated,
220+
existing,
219221
},
220222
);
221223
}

crates/bevy_ecs/src/bundle.rs

+77-14
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ impl SparseSetIndex for BundleId {
307307
}
308308
}
309309

310+
// What to do on insertion if component already exists
311+
#[derive(Clone, Copy, Eq, PartialEq)]
312+
pub(crate) enum InsertMode {
313+
/// Any existing components of a matching type will be overwritten.
314+
Replace,
315+
/// Any existing components of a matching type will kept unchanged.
316+
Keep,
317+
}
318+
310319
/// Stores metadata associated with a specific type of [`Bundle`] for a given [`World`].
311320
///
312321
/// [`World`]: crate::world::World
@@ -410,6 +419,7 @@ impl BundleInfo {
410419
table_row: TableRow,
411420
change_tick: Tick,
412421
bundle: T,
422+
insert_mode: InsertMode,
413423
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
414424
) {
415425
// NOTE: get_components calls this closure on each component in "bundle order".
@@ -425,8 +435,8 @@ impl BundleInfo {
425435
unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
426436
// SAFETY: bundle_component is a valid index for this bundle
427437
let status = unsafe { bundle_component_status.get_status(bundle_component) };
428-
match status {
429-
ComponentStatus::Added => {
438+
match (status, insert_mode) {
439+
(ComponentStatus::Added, _) => {
430440
column.initialize(
431441
table_row,
432442
component_ptr,
@@ -435,7 +445,7 @@ impl BundleInfo {
435445
caller,
436446
);
437447
}
438-
ComponentStatus::Mutated => {
448+
(ComponentStatus::Existing, InsertMode::Replace) => {
439449
column.replace(
440450
table_row,
441451
component_ptr,
@@ -444,6 +454,9 @@ impl BundleInfo {
444454
caller,
445455
);
446456
}
457+
(ComponentStatus::Existing, InsertMode::Keep) => {
458+
column.drop(component_ptr);
459+
}
447460
}
448461
}
449462
StorageType::SparseSet => {
@@ -489,7 +502,7 @@ impl BundleInfo {
489502
let current_archetype = &mut archetypes[archetype_id];
490503
for component_id in self.component_ids.iter().cloned() {
491504
if current_archetype.contains(component_id) {
492-
bundle_status.push(ComponentStatus::Mutated);
505+
bundle_status.push(ComponentStatus::Existing);
493506
mutated.push(component_id);
494507
} else {
495508
bundle_status.push(ComponentStatus::Added);
@@ -692,6 +705,7 @@ impl<'w> BundleInserter<'w> {
692705
entity: Entity,
693706
location: EntityLocation,
694707
bundle: T,
708+
insert_mode: InsertMode,
695709
#[cfg(feature = "track_change_detection")] caller: &'static core::panic::Location<'static>,
696710
) -> EntityLocation {
697711
let bundle_info = self.bundle_info.as_ref();
@@ -705,13 +719,15 @@ impl<'w> BundleInserter<'w> {
705719
// SAFETY: Mutable references do not alias and will be dropped after this block
706720
let mut deferred_world = self.world.into_deferred();
707721

708-
deferred_world.trigger_on_replace(
709-
archetype,
710-
entity,
711-
add_bundle.mutated.iter().copied(),
712-
);
713-
if archetype.has_replace_observer() {
714-
deferred_world.trigger_observers(ON_REPLACE, entity, &add_bundle.mutated);
722+
if insert_mode == InsertMode::Replace {
723+
deferred_world.trigger_on_replace(
724+
archetype,
725+
entity,
726+
add_bundle.existing.iter().copied(),
727+
);
728+
if archetype.has_replace_observer() {
729+
deferred_world.trigger_observers(ON_REPLACE, entity, &add_bundle.existing);
730+
}
715731
}
716732
}
717733

@@ -735,6 +751,7 @@ impl<'w> BundleInserter<'w> {
735751
location.table_row,
736752
self.change_tick,
737753
bundle,
754+
insert_mode,
738755
#[cfg(feature = "track_change_detection")]
739756
caller,
740757
);
@@ -775,6 +792,7 @@ impl<'w> BundleInserter<'w> {
775792
result.table_row,
776793
self.change_tick,
777794
bundle,
795+
insert_mode,
778796
#[cfg(feature = "track_change_detection")]
779797
caller,
780798
);
@@ -856,6 +874,7 @@ impl<'w> BundleInserter<'w> {
856874
move_result.new_row,
857875
self.change_tick,
858876
bundle,
877+
insert_mode,
859878
#[cfg(feature = "track_change_detection")]
860879
caller,
861880
);
@@ -875,9 +894,34 @@ impl<'w> BundleInserter<'w> {
875894
if new_archetype.has_add_observer() {
876895
deferred_world.trigger_observers(ON_ADD, entity, &add_bundle.added);
877896
}
878-
deferred_world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components());
879-
if new_archetype.has_insert_observer() {
880-
deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.components());
897+
match insert_mode {
898+
InsertMode::Replace => {
899+
// insert triggers for both new and existing components if we're replacing them
900+
deferred_world.trigger_on_insert(
901+
new_archetype,
902+
entity,
903+
bundle_info.iter_components(),
904+
);
905+
if new_archetype.has_insert_observer() {
906+
deferred_world.trigger_observers(
907+
ON_INSERT,
908+
entity,
909+
bundle_info.components(),
910+
);
911+
}
912+
}
913+
InsertMode::Keep => {
914+
// insert triggers only for new components if we're not replacing them (since
915+
// nothing is actually inserted).
916+
deferred_world.trigger_on_insert(
917+
new_archetype,
918+
entity,
919+
add_bundle.added.iter().cloned(),
920+
);
921+
if new_archetype.has_insert_observer() {
922+
deferred_world.trigger_observers(ON_INSERT, entity, &add_bundle.added);
923+
}
924+
}
881925
}
882926
}
883927

@@ -977,6 +1021,7 @@ impl<'w> BundleSpawner<'w> {
9771021
table_row,
9781022
self.change_tick,
9791023
bundle,
1024+
InsertMode::Replace,
9801025
#[cfg(feature = "track_change_detection")]
9811026
caller,
9821027
);
@@ -1230,6 +1275,9 @@ mod tests {
12301275
#[derive(Component)]
12311276
struct D;
12321277

1278+
#[derive(Component, Eq, PartialEq, Debug)]
1279+
struct V(&'static str); // component with a value
1280+
12331281
#[derive(Resource, Default)]
12341282
struct R(usize);
12351283

@@ -1302,6 +1350,7 @@ mod tests {
13021350
world.init_resource::<R>();
13031351
let mut entity = world.entity_mut(entity);
13041352
entity.insert(A);
1353+
entity.insert_if_new(A); // this will not trigger on_replace or on_insert
13051354
entity.flush();
13061355
assert_eq!(2, world.resource::<R>().0);
13071356
}
@@ -1371,4 +1420,18 @@ mod tests {
13711420
world.spawn(A).flush();
13721421
assert_eq!(4, world.resource::<R>().0);
13731422
}
1423+
1424+
#[test]
1425+
fn insert_if_new() {
1426+
let mut world = World::new();
1427+
let id = world.spawn(V("one")).id();
1428+
let mut entity = world.entity_mut(id);
1429+
entity.insert_if_new(V("two"));
1430+
entity.insert_if_new((A, V("three")));
1431+
entity.flush();
1432+
// should still contain "one"
1433+
let entity = world.entity(id);
1434+
assert!(entity.contains::<A>());
1435+
assert_eq!(entity.get(), Some(&V("one")));
1436+
}
13741437
}

crates/bevy_ecs/src/storage/blob_vec.rs

+8
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,14 @@ impl BlobVec {
437437
}
438438
}
439439
}
440+
441+
/// Get the `drop` argument that was passed to `BlobVec::new`.
442+
///
443+
/// Callers can use this if they have a type-erased pointer of the correct
444+
/// type to add to this [`BlobVec`], which they just want to drop instead.
445+
pub fn get_drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
446+
self.drop
447+
}
440448
}
441449

442450
impl Drop for BlobVec {

crates/bevy_ecs/src/storage/table.rs

+14
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,20 @@ impl Column {
232232
}
233233
}
234234

235+
/// Call [`drop`] on a value.
236+
///
237+
/// # Safety
238+
/// `data` must point to the same type that this table stores, so the
239+
/// correct drop function is called.
240+
#[inline]
241+
pub(crate) unsafe fn drop(&self, data: OwningPtr<'_>) {
242+
if let Some(drop) = self.data.get_drop() {
243+
// Safety: we're using the same drop fn that the BlobVec would
244+
// if we inserted the data instead of dropping it.
245+
unsafe { drop(data) }
246+
}
247+
}
248+
235249
/// Gets the current number of elements stored in the column.
236250
#[inline]
237251
pub fn len(&self) -> usize {

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

+26-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::panic::Location;
55
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
66
use crate::{
77
self as bevy_ecs,
8-
bundle::Bundle,
8+
bundle::{Bundle, InsertMode},
99
component::{ComponentId, ComponentInfo},
1010
entity::{Entities, Entity},
1111
event::Event,
@@ -895,6 +895,7 @@ impl EntityCommands<'_> {
895895
/// Adds a [`Bundle`] of components to the entity.
896896
///
897897
/// This will overwrite any previous value(s) of the same component type.
898+
/// See [`EntityCommands::insert_if_new`] to keep the old value instead.
898899
///
899900
/// # Panics
900901
///
@@ -945,7 +946,24 @@ impl EntityCommands<'_> {
945946
/// ```
946947
#[track_caller]
947948
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
948-
self.add(insert(bundle))
949+
self.add(insert(bundle, InsertMode::Replace))
950+
}
951+
952+
/// Adds a [`Bundle`] of components to the entity without overwriting.
953+
///
954+
/// This is the same as [`EntityCommands::insert`], but in case of duplicate
955+
/// components will leave the old values instead of replacing them with new
956+
/// ones.
957+
///
958+
/// # Panics
959+
///
960+
/// The command will panic when applied if the associated entity does not
961+
/// exist.
962+
///
963+
/// To avoid a panic in this case, use the command [`Self::try_insert`]
964+
/// instead.
965+
pub fn insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self {
966+
self.add(insert(bundle, InsertMode::Keep))
949967
}
950968

951969
/// Adds a dynamic component to an entity.
@@ -1044,7 +1062,7 @@ impl EntityCommands<'_> {
10441062
/// ```
10451063
#[track_caller]
10461064
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
1047-
self.add(try_insert(bundle))
1065+
self.add(try_insert(bundle, InsertMode::Replace))
10481066
}
10491067

10501068
/// Removes a [`Bundle`] of components from the entity.
@@ -1321,12 +1339,13 @@ fn despawn() -> impl EntityCommand {
13211339

13221340
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity.
13231341
#[track_caller]
1324-
fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
1342+
fn insert<T: Bundle>(bundle: T, mode: InsertMode) -> impl EntityCommand {
13251343
let caller = core::panic::Location::caller();
13261344
move |entity: Entity, world: &mut World| {
13271345
if let Some(mut entity) = world.get_entity_mut(entity) {
13281346
entity.insert_with_caller(
13291347
bundle,
1348+
mode,
13301349
#[cfg(feature = "track_change_detection")]
13311350
caller,
13321351
);
@@ -1337,14 +1356,16 @@ fn insert<T: Bundle>(bundle: T) -> impl EntityCommand {
13371356
}
13381357

13391358
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
1359+
/// Does nothing if the entity does not exist.
13401360
#[track_caller]
1341-
fn try_insert(bundle: impl Bundle) -> impl EntityCommand {
1361+
fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
13421362
#[cfg(feature = "track_change_detection")]
13431363
let caller = core::panic::Location::caller();
13441364
move |entity, world: &mut World| {
13451365
if let Some(mut entity) = world.get_entity_mut(entity) {
13461366
entity.insert_with_caller(
13471367
bundle,
1368+
mode,
13481369
#[cfg(feature = "track_change_detection")]
13491370
caller,
13501371
);

0 commit comments

Comments
 (0)