From 8245efcc81a81d65f82b59ef3879da3b4c578d8b Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Fri, 30 Apr 2021 20:16:38 -0400 Subject: [PATCH 1/8] Add "try" variants to ecs insertion functions to control overwriting behaviour --- crates/bevy_ecs/src/bundle.rs | 44 ++++++ crates/bevy_ecs/src/system/commands.rs | 105 ++++++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 175 +++++++++++++++--------- crates/bevy_ecs/src/world/mod.rs | 26 +++- 4 files changed, 279 insertions(+), 71 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index d640581c969aa..70ef6b45e6951 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -166,6 +166,50 @@ impl BundleInfo { }); } + /// Like write_components, but does not overwrite existing components on the entity + /// # Safety + /// table row must exist, entity must be valid + #[allow(clippy::clippy::too_many_arguments)] + #[inline] + pub(crate) unsafe fn write_additional_components( + &self, + sparse_sets: &mut SparseSets, + entity: Entity, + table: &Table, + table_row: usize, + bundle_status: &[ComponentStatus], + bundle: T, + change_tick: u32, + ) { + // NOTE: get_components calls this closure on each component in "bundle order". + // bundle_info.component_ids are also in "bundle order" + let mut bundle_component = 0; + bundle.get_components(|component_ptr| { + // SAFE: component_id was initialized by get_dynamic_bundle_info + let component_id = *self.component_ids.get_unchecked(bundle_component); + let component_status = bundle_status.get_unchecked(bundle_component); + match self.storage_types[bundle_component] { + StorageType::Table => match component_status { + ComponentStatus::Added => { + let column = table.get_column(component_id).unwrap(); + column.set_unchecked(table_row, component_ptr); + let column_status = column.get_ticks_unchecked_mut(table_row); + *column_status = ComponentTicks::new(change_tick); + } + ComponentStatus::Mutated => {} + }, + StorageType::SparseSet => match component_status { + ComponentStatus::Added => { + let sparse_set = sparse_sets.get_mut(component_id).unwrap(); + sparse_set.insert(entity, component_ptr, change_tick); + } + ComponentStatus::Mutated => {} + }, + } + bundle_component += 1; + }); + } + #[inline] pub fn id(&self) -> BundleId { self.id diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 627b9e4e73531..3433e36cc4672 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -206,6 +206,15 @@ impl<'a, 'b> EntityCommands<'a, 'b> { self } + /// Adds a bundle of components to the current entity, skipping ones that already exist. + pub fn try_insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { + self.commands.add(TryInsertBundle { + entity: self.entity, + bundle, + }); + self + } + /// Adds a single [`Component`] to the current entity. /// /// @@ -246,6 +255,15 @@ impl<'a, 'b> EntityCommands<'a, 'b> { self } + /// Like insert, but does not overwrite components which already exist on the entity + pub fn try_insert(&mut self, component: impl Component) -> &mut Self { + self.commands.add(TryInsert { + entity: self.entity, + component, + }); + self + } + /// See [`EntityMut::remove_bundle`](crate::world::EntityMut::remove_bundle). pub fn remove_bundle(&mut self) -> &mut Self where @@ -342,6 +360,20 @@ where } } +pub struct TryInsertBundle { + entity: Entity, + bundle: T, +} + +impl Command for TryInsertBundle +where + T: Bundle + 'static, +{ + fn write(self: Box, world: &mut World) { + world.entity_mut(self.entity).try_insert_bundle(self.bundle); + } +} + #[derive(Debug)] pub struct Insert { pub entity: Entity, @@ -357,6 +389,21 @@ where } } +#[derive(Debug)] +pub(crate) struct TryInsert { + entity: Entity, + component: T, +} + +impl Command for TryInsert +where + T: Component, +{ + fn write(self: Box, world: &mut World) { + world.entity_mut(self.entity).try_insert(self.component); + } +} + #[derive(Debug)] pub struct Remove { entity: Entity, @@ -451,6 +498,64 @@ mod tests { assert_eq!(results2, vec![]); } + #[test] + fn try_insert_components_not_present() { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + let _ = Commands::new(&mut command_queue, &world) + .spawn() + .try_insert_bundle((2u32, 4u64)) + .id(); + + command_queue.apply(&mut world); + assert!(world.entities().len() == 1); + let results = world + .query::<(&u32, &u64)>() + .iter(&world) + .map(|(a, b)| (*a, *b)) + .collect::>(); + assert_eq!(results, vec![(2u32, 4u64)]); + } + + #[test] + fn try_insert_components_present() { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + let _ = Commands::new(&mut command_queue, &world) + .spawn_bundle((1u32, 2u64)) + .try_insert_bundle((2u32, 4u64)) + .id(); + + command_queue.apply(&mut world); + assert!(world.entities().len() == 1); + let results = world + .query::<(&u32, &u64)>() + .iter(&world) + .map(|(a, b)| (*a, *b)) + .collect::>(); + assert_eq!(results, vec![(1u32, 2u64)]); + } + + #[test] + fn try_insert_components_some_present() { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + let _ = Commands::new(&mut command_queue, &world) + .spawn() + .insert(1u32) + .try_insert_bundle((2u32, 4u64)) + .id(); + + command_queue.apply(&mut world); + assert!(world.entities().len() == 1); + let results = world + .query::<(&u32, &u64)>() + .iter(&world) + .map(|(a, b)| (*a, *b)) + .collect::>(); + assert_eq!(results, vec![(1u32, 4u64)]); + } + #[test] fn remove_components() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index f8cb1dc60a183..d65b9a6a5cb94 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -194,76 +194,49 @@ impl<'w> EntityMut<'w> { let bundle_info = self.world.bundles.init_info::(components); let current_location = self.location; - // Use a non-generic function to cut down on monomorphization - unsafe fn get_insert_bundle_info<'a>( - entities: &mut Entities, - archetypes: &'a mut Archetypes, - components: &mut Components, - storages: &mut Storages, - bundle_info: &BundleInfo, - current_location: EntityLocation, - entity: Entity, - ) -> (&'a Archetype, &'a Vec, EntityLocation) { - // SAFE: component ids in `bundle_info` and self.location are valid - let new_archetype_id = add_bundle_to_archetype( + let (archetype, bundle_status, new_location) = unsafe { + Self::get_insert_bundle_info( + entities, archetypes, - storages, components, - current_location.archetype_id, + storages, bundle_info, - ); - if new_archetype_id == current_location.archetype_id { - let archetype = &archetypes[current_location.archetype_id]; - let edge = archetype.edges().get_add_bundle(bundle_info.id).unwrap(); - (archetype, &edge.bundle_status, current_location) - } else { - let (old_table_row, old_table_id) = { - let old_archetype = &mut archetypes[current_location.archetype_id]; - let result = old_archetype.swap_remove(current_location.index); - if let Some(swapped_entity) = result.swapped_entity { - entities.meta[swapped_entity.id as usize].location = current_location; - } - (result.table_row, old_archetype.table_id()) - }; - - let new_table_id = archetypes[new_archetype_id].table_id(); - - let new_location = if old_table_id == new_table_id { - archetypes[new_archetype_id].allocate(entity, old_table_row) - } else { - let (old_table, new_table) = - storages.tables.get_2_mut(old_table_id, new_table_id); - // PERF: store "non bundle" components in edge, then just move those to avoid - // redundant copies - let move_result = - old_table.move_to_superset_unchecked(old_table_row, new_table); - - let new_location = - archetypes[new_archetype_id].allocate(entity, move_result.new_row); - // if an entity was moved into this entity's table spot, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = entities.get(swapped_entity).unwrap(); - archetypes[swapped_location.archetype_id] - .set_entity_table_row(swapped_location.index, old_table_row); - } - new_location - }; - - entities.meta[entity.id as usize].location = new_location; - let (old_archetype, new_archetype) = - archetypes.get_2_mut(current_location.archetype_id, new_archetype_id); - let edge = old_archetype - .edges() - .get_add_bundle(bundle_info.id) - .unwrap(); - (&*new_archetype, &edge.bundle_status, new_location) - - // Sparse set components are intentionally ignored here. They don't need to move - } - } + current_location, + entity, + ) + }; + self.location = new_location; + + let table = &storages.tables[archetype.table_id()]; + let table_row = archetype.entity_table_row(new_location.index); + // SAFE: table row is valid + unsafe { + bundle_info.write_components( + &mut storages.sparse_sets, + entity, + table, + table_row, + bundle_status, + bundle, + change_tick, + ) + }; + self + } + + pub fn try_insert_bundle(&mut self, bundle: T) -> &mut Self { + let entity = self.entity; + let change_tick = self.world.change_tick(); + let entities = &mut self.world.entities; + let archetypes = &mut self.world.archetypes; + let components = &mut self.world.components; + let storages = &mut self.world.storages; + + let bundle_info = self.world.bundles.init_info::(components); + let current_location = self.location; let (archetype, bundle_status, new_location) = unsafe { - get_insert_bundle_info( + Self::get_insert_bundle_info( entities, archetypes, components, @@ -279,7 +252,7 @@ impl<'w> EntityMut<'w> { let table_row = archetype.entity_table_row(new_location.index); // SAFE: table row is valid unsafe { - bundle_info.write_components( + bundle_info.write_additional_components( &mut storages.sparse_sets, entity, table, @@ -292,6 +265,72 @@ impl<'w> EntityMut<'w> { self } + // Use a non-generic function to cut down on monomorphization + unsafe fn get_insert_bundle_info<'a>( + entities: &mut Entities, + archetypes: &'a mut Archetypes, + components: &mut Components, + storages: &mut Storages, + bundle_info: &BundleInfo, + current_location: EntityLocation, + entity: Entity, + ) -> (&'a Archetype, &'a Vec, EntityLocation) { + // SAFE: component ids in `bundle_info` and self.location are valid + let new_archetype_id = add_bundle_to_archetype( + archetypes, + storages, + components, + current_location.archetype_id, + bundle_info, + ); + if new_archetype_id == current_location.archetype_id { + let archetype = &archetypes[current_location.archetype_id]; + let edge = archetype.edges().get_add_bundle(bundle_info.id).unwrap(); + (archetype, &edge.bundle_status, current_location) + } else { + let (old_table_row, old_table_id) = { + let old_archetype = &mut archetypes[current_location.archetype_id]; + let result = old_archetype.swap_remove(current_location.index); + if let Some(swapped_entity) = result.swapped_entity { + entities.meta[swapped_entity.id as usize].location = current_location; + } + (result.table_row, old_archetype.table_id()) + }; + + let new_table_id = archetypes[new_archetype_id].table_id(); + + let new_location = if old_table_id == new_table_id { + archetypes[new_archetype_id].allocate(entity, old_table_row) + } else { + let (old_table, new_table) = storages.tables.get_2_mut(old_table_id, new_table_id); + // PERF: store "non bundle" components in edge, then just move those to avoid + // redundant copies + let move_result = old_table.move_to_superset_unchecked(old_table_row, new_table); + + let new_location = + archetypes[new_archetype_id].allocate(entity, move_result.new_row); + // if an entity was moved into this entity's table spot, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = entities.get(swapped_entity).unwrap(); + archetypes[swapped_location.archetype_id] + .set_entity_table_row(swapped_location.index, old_table_row); + } + new_location + }; + + entities.meta[entity.id as usize].location = new_location; + let (old_archetype, new_archetype) = + archetypes.get_2_mut(current_location.archetype_id, new_archetype_id); + let edge = old_archetype + .edges() + .get_add_bundle(bundle_info.id) + .unwrap(); + (&*new_archetype, &edge.bundle_status, new_location) + + // Sparse set components are intentionally ignored here. They don't need to move + } + } + pub fn remove_bundle(&mut self) -> Option { let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -461,6 +500,10 @@ impl<'w> EntityMut<'w> { self.insert_bundle((value,)) } + pub fn try_insert(&mut self, value: T) -> &mut Self { + self.try_insert_bundle((value,)) + } + pub fn remove(&mut self) -> Option { self.remove_bundle::<(T,)>().map(|v| v.0) } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2646f58a1dbfa..04b4224bd1afe 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -517,13 +517,24 @@ impl World { } } - /// Inserts a new resource with the given `value`. + /// Inserts a new resource with the given `value`, + /// overwriting an existing resource of type T. /// Resources are "unique" data of a given type. #[inline] pub fn insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value) }; + unsafe { self.insert_resource_with_id(component_id, value, true) }; + } + + /// Inserts a new resource with the given `value`. + /// If a resource of type T already exists, the new resource is not inserted. + /// Resources are "unique" data of a given type. + #[inline] + pub fn try_insert_resource(&mut self, value: T) { + let component_id = self.components.get_or_insert_resource_id::(); + // SAFE: component_id just initialized and corresponds to resource of type T + unsafe { self.insert_resource_with_id(component_id, value, false) }; } /// Inserts a new non-send resource with the given `value`. @@ -533,7 +544,7 @@ impl World { self.validate_non_send_access::(); let component_id = self.components.get_or_insert_non_send_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value) }; + unsafe { self.insert_resource_with_id(component_id, value, true) }; } /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. @@ -781,7 +792,12 @@ impl World { /// # Safety /// `component_id` must be valid and correspond to a resource component of type T #[inline] - unsafe fn insert_resource_with_id(&mut self, component_id: ComponentId, mut value: T) { + unsafe fn insert_resource_with_id( + &mut self, + component_id: ComponentId, + mut value: T, + overwrite_existing: bool, + ) { let change_tick = self.change_tick(); let column = self.initialize_resource_internal(component_id); if column.is_empty() { @@ -794,7 +810,7 @@ impl World { std::mem::forget(value); // SAFE: index was just allocated above *column.get_ticks_unchecked_mut(row) = ComponentTicks::new(change_tick); - } else { + } else if overwrite_existing { // SAFE: column is of type T and has already been allocated *column.get_unchecked(0).cast::() = value; column.get_ticks_unchecked_mut(0).set_changed(change_tick); From be5ffbd65ae0f86424f6f655d6f7f4870718c2fc Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Fri, 30 Apr 2021 22:01:30 -0400 Subject: [PATCH 2/8] Make documentation wording consistent between insert/insert_bundle and their try equivalents --- crates/bevy_ecs/src/system/commands.rs | 24 ++++++++++++++++++++++-- crates/bevy_ecs/src/world/mod.rs | 3 +-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 3433e36cc4672..4fa803b2f9cd7 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -198,6 +198,10 @@ impl<'a, 'b> EntityCommands<'a, 'b> { } /// Adds a [`Bundle`] of components to the current entity. + /// + /// If any of the components in the [`Bundle`] are already present on the entity, + /// those components will be overwritten. To add only the components in the Bundle which + /// are not already present on the entity, see [try_insert_bundle](Self::try_insert_bundle()). pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { self.commands.add(InsertBundle { entity: self.entity, @@ -206,7 +210,11 @@ impl<'a, 'b> EntityCommands<'a, 'b> { self } - /// Adds a bundle of components to the current entity, skipping ones that already exist. + /// Adds a [`Bundle`] of components to the current entity. + /// + /// If any of the components in the [`Bundle`] are already present on the entity, + /// those components will be silenty skipped. To overwrite components already present + /// on the entity with the value in the Bundle, see [insert_bundle](Self::insert_bundle()). pub fn try_insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { self.commands.add(TryInsertBundle { entity: self.entity, @@ -217,6 +225,8 @@ impl<'a, 'b> EntityCommands<'a, 'b> { /// Adds a single [`Component`] to the current entity. /// + /// If an instance of the Component is already present on the entity, that component will be overwritten. + /// For adding a [`Component`] to an entity only when an instance is not already present, see [try_insert](Self::try_insert()). /// /// # Warning /// @@ -255,7 +265,17 @@ impl<'a, 'b> EntityCommands<'a, 'b> { self } - /// Like insert, but does not overwrite components which already exist on the entity + /// Adds a single [`Component`] to the current entity. + /// + /// If an instance of the Component is already present on the entity, `try_insert` will silently return without changing it. + /// For adding a [`Component`] to an entity and overwriting the existing instance, see [insert](Self::insert()). + /// + /// # Warning + /// + /// It's possible to call this with a bundle, but this is likely not intended and + /// [`Self::try_insert_bundle`] should be used instead. If `try_insert` is called with a bundle, the + /// bundle itself will be added as a component instead of the bundles' inner components each + /// being added. pub fn try_insert(&mut self, component: impl Component) -> &mut Self { self.commands.add(TryInsert { entity: self.entity, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 04b4224bd1afe..96e87012cc21c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -518,7 +518,7 @@ impl World { } /// Inserts a new resource with the given `value`, - /// overwriting an existing resource of type T. + /// overwriting any existing resource of type T. /// Resources are "unique" data of a given type. #[inline] pub fn insert_resource(&mut self, value: T) { @@ -529,7 +529,6 @@ impl World { /// Inserts a new resource with the given `value`. /// If a resource of type T already exists, the new resource is not inserted. - /// Resources are "unique" data of a given type. #[inline] pub fn try_insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); From 0533c6657b8ab4ff45b19d726e2d8478dafd81e7 Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Fri, 30 Apr 2021 22:09:48 -0400 Subject: [PATCH 3/8] Add dedicated tests for try_insert --- crates/bevy_ecs/src/system/commands.rs | 45 +++++++++++++++++++++++-- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 4fa803b2f9cd7..d0e4f180ff1da 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -519,7 +519,46 @@ mod tests { } #[test] - fn try_insert_components_not_present() { + fn try_insert_component_not_present() { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + let _ = Commands::new(&mut command_queue, &world) + .spawn() + .try_insert(2u32) + .id(); + + command_queue.apply(&mut world); + assert!(world.entities().len() == 1); + let results = world + .query::<&u32>() + .iter(&world) + .copied() + .collect::>(); + assert_eq!(results, vec![2u32]); + } + + #[test] + fn try_insert_component_present() { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + let _ = Commands::new(&mut command_queue, &world) + .spawn() + .insert(1u32) + .try_insert(2u32) + .id(); + + command_queue.apply(&mut world); + assert!(world.entities().len() == 1); + let results = world + .query::<&u32>() + .iter(&world) + .copied() + .collect::>(); + assert_eq!(results, vec![1u32]); + } + + #[test] + fn try_insert_bundle_components_not_present() { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let _ = Commands::new(&mut command_queue, &world) @@ -538,7 +577,7 @@ mod tests { } #[test] - fn try_insert_components_present() { + fn try_insert_bundle_components_all_present() { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let _ = Commands::new(&mut command_queue, &world) @@ -557,7 +596,7 @@ mod tests { } #[test] - fn try_insert_components_some_present() { + fn try_insert_bundle_components_some_present() { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let _ = Commands::new(&mut command_queue, &world) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d65b9a6a5cb94..ba1747a8863af 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -231,7 +231,7 @@ impl<'w> EntityMut<'w> { let archetypes = &mut self.world.archetypes; let components = &mut self.world.components; let storages = &mut self.world.storages; - + let bundle_info = self.world.bundles.init_info::(components); let current_location = self.location; From 36d65096921f216c40e98ad904e7e50f99b98a38 Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Sun, 2 May 2021 20:54:38 -0400 Subject: [PATCH 4/8] Remove "write_additional_components" and use enum for control flow --- crates/bevy_ecs/src/bundle.rs | 72 +++++++----------------- crates/bevy_ecs/src/component/mod.rs | 11 ++++ crates/bevy_ecs/src/world/entity_ref.rs | 8 ++- crates/bevy_ecs/src/world/mod.rs | 14 ++--- crates/bevy_ecs/src/world/spawn_batch.rs | 2 + 5 files changed, 45 insertions(+), 62 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 70ef6b45e6951..df6a3f51006bb 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -2,7 +2,10 @@ pub use bevy_ecs_macros::Bundle; use crate::{ archetype::ComponentStatus, - component::{Component, ComponentId, ComponentTicks, Components, StorageType, TypeInfo}, + component::{ + Component, ComponentCollision, ComponentId, ComponentTicks, Components, StorageType, + TypeInfo, + }, entity::Entity, storage::{SparseSetIndex, SparseSets, Table}, }; @@ -135,6 +138,7 @@ impl BundleInfo { bundle_status: &[ComponentStatus], bundle: T, change_tick: u32, + overwrite_existing: ComponentCollision, ) { // NOTE: get_components calls this closure on each component in "bundle order". // bundle_info.component_ids are also in "bundle order" @@ -144,67 +148,29 @@ impl BundleInfo { let component_id = *self.component_ids.get_unchecked(bundle_component); let component_status = bundle_status.get_unchecked(bundle_component); match self.storage_types[bundle_component] { - StorageType::Table => { - let column = table.get_column(component_id).unwrap(); - column.set_unchecked(table_row, component_ptr); - let column_status = column.get_ticks_unchecked_mut(table_row); - match component_status { - ComponentStatus::Added => { - *column_status = ComponentTicks::new(change_tick); - } - ComponentStatus::Mutated => { - column_status.set_changed(change_tick); - } - } - } - StorageType::SparseSet => { - let sparse_set = sparse_sets.get_mut(component_id).unwrap(); - sparse_set.insert(entity, component_ptr, change_tick); - } - } - bundle_component += 1; - }); - } - - /// Like write_components, but does not overwrite existing components on the entity - /// # Safety - /// table row must exist, entity must be valid - #[allow(clippy::clippy::too_many_arguments)] - #[inline] - pub(crate) unsafe fn write_additional_components( - &self, - sparse_sets: &mut SparseSets, - entity: Entity, - table: &Table, - table_row: usize, - bundle_status: &[ComponentStatus], - bundle: T, - change_tick: u32, - ) { - // NOTE: get_components calls this closure on each component in "bundle order". - // bundle_info.component_ids are also in "bundle order" - let mut bundle_component = 0; - bundle.get_components(|component_ptr| { - // SAFE: component_id was initialized by get_dynamic_bundle_info - let component_id = *self.component_ids.get_unchecked(bundle_component); - let component_status = bundle_status.get_unchecked(bundle_component); - match self.storage_types[bundle_component] { - StorageType::Table => match component_status { - ComponentStatus::Added => { + StorageType::Table => match (component_status, overwrite_existing) { + (ComponentStatus::Added, _) => { let column = table.get_column(component_id).unwrap(); column.set_unchecked(table_row, component_ptr); let column_status = column.get_ticks_unchecked_mut(table_row); *column_status = ComponentTicks::new(change_tick); } - ComponentStatus::Mutated => {} + (ComponentStatus::Mutated, ComponentCollision::Overwrite) => { + let column = table.get_column(component_id).unwrap(); + column.set_unchecked(table_row, component_ptr); + let column_status = column.get_ticks_unchecked_mut(table_row); + column_status.set_changed(change_tick); + } + (ComponentStatus::Mutated, ComponentCollision::Skip) => {} }, - StorageType::SparseSet => match component_status { - ComponentStatus::Added => { + StorageType::SparseSet => { + if matches!(component_status, ComponentStatus::Added) + || matches!(overwrite_existing, ComponentCollision::Overwrite) + { let sparse_set = sparse_sets.get_mut(component_id).unwrap(); sparse_set.insert(entity, component_ptr, change_tick); } - ComponentStatus::Mutated => {} - }, + } } bundle_component += 1; }); diff --git a/crates/bevy_ecs/src/component/mod.rs b/crates/bevy_ecs/src/component/mod.rs index 3ee166d9c0aee..9c993b13d2e3e 100644 --- a/crates/bevy_ecs/src/component/mod.rs +++ b/crates/bevy_ecs/src/component/mod.rs @@ -370,3 +370,14 @@ fn check_tick(last_change_tick: &mut u32, change_tick: u32) { *last_change_tick = change_tick.wrapping_sub(MAX_DELTA); } } + +/// Used by [BundleInfo::write_components](crate::bundle::BundleInfo::write_components()) +/// and [World::write_components](crate::world::World::insert_resource_with_id()) to control +/// how collisions between newly inserted and existing component types should be handled. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ComponentCollision { + /// Overwrite existing component/resource of the same type. + Overwrite, + /// Skip and do not write the new component if it already exists. + Skip, +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ba1747a8863af..047d0ec26f410 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,7 +1,9 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes, ComponentStatus}, bundle::{Bundle, BundleInfo}, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + component::{ + Component, ComponentCollision, ComponentId, ComponentTicks, Components, StorageType, + }, entity::{Entities, Entity, EntityLocation}, storage::{SparseSet, Storages}, world::{Mut, World}, @@ -219,6 +221,7 @@ impl<'w> EntityMut<'w> { bundle_status, bundle, change_tick, + ComponentCollision::Overwrite, ) }; self @@ -252,7 +255,7 @@ impl<'w> EntityMut<'w> { let table_row = archetype.entity_table_row(new_location.index); // SAFE: table row is valid unsafe { - bundle_info.write_additional_components( + bundle_info.write_components( &mut storages.sparse_sets, entity, table, @@ -260,6 +263,7 @@ impl<'w> EntityMut<'w> { bundle_status, bundle, change_tick, + ComponentCollision::Skip, ) }; self diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 96e87012cc21c..5ca5de567513b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -12,8 +12,8 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes}, bundle::{Bundle, Bundles}, component::{ - Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, ComponentsError, - StorageType, + Component, ComponentCollision, ComponentDescriptor, ComponentId, ComponentTicks, + Components, ComponentsError, StorageType, }, entity::{Entities, Entity}, query::{FilterFetch, QueryState, WorldQuery}, @@ -524,7 +524,7 @@ impl World { pub fn insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, true) }; + unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Overwrite) }; } /// Inserts a new resource with the given `value`. @@ -533,7 +533,7 @@ impl World { pub fn try_insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, false) }; + unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Skip) }; } /// Inserts a new non-send resource with the given `value`. @@ -543,7 +543,7 @@ impl World { self.validate_non_send_access::(); let component_id = self.components.get_or_insert_non_send_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, true) }; + unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Overwrite) }; } /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. @@ -795,7 +795,7 @@ impl World { &mut self, component_id: ComponentId, mut value: T, - overwrite_existing: bool, + overwrite_existing: ComponentCollision, ) { let change_tick = self.change_tick(); let column = self.initialize_resource_internal(component_id); @@ -809,7 +809,7 @@ impl World { std::mem::forget(value); // SAFE: index was just allocated above *column.get_ticks_unchecked_mut(row) = ComponentTicks::new(change_tick); - } else if overwrite_existing { + } else if matches!(overwrite_existing, ComponentCollision::Overwrite) { // SAFE: column is of type T and has already been allocated *column.get_unchecked(0).cast::() = value; column.get_ticks_unchecked_mut(0).set_changed(change_tick); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 95830dc72b696..89d36a564ce13 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -1,6 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeId, ComponentStatus}, bundle::{Bundle, BundleInfo}, + component::ComponentCollision, entity::{Entities, Entity}, storage::{SparseSets, Table}, world::{add_bundle_to_archetype, World}, @@ -104,6 +105,7 @@ where self.bundle_status, bundle, self.change_tick, + ComponentCollision::Overwrite, ); self.entities.meta[entity.id as usize].location = location; } From 625dd43486450033e6264fbd47d50adcdfc4ba74 Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Sun, 2 May 2021 21:06:21 -0400 Subject: [PATCH 5/8] Add tests for both "try_insert_resource" cases --- crates/bevy_ecs/src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f45c6790a5780..06d36aff33172 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -941,6 +941,27 @@ mod tests { ); } + #[test] + fn try_insert_resource_no_collision() { + let mut world = World::default(); + world.try_insert_resource(64u64); + assert_eq!( + *world.get_resource::().expect("Resource exists"), + 64u64 + ); + } + + #[test] + fn try_insert_resource_collision() { + let mut world = World::default(); + world.insert_resource(32u64); + world.try_insert_resource(64u64); + assert_eq!( + *world.get_resource::().expect("Resource exists"), + 32u64 + ); + } + #[test] fn remove_intersection() { let mut world = World::default(); From 38e838a176e23d7ad06a0d191d2479fe4a90816b Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Sun, 2 May 2021 21:57:31 -0400 Subject: [PATCH 6/8] Remove unnecessary code duplication in BundleInfo::write_components --- crates/bevy_ecs/src/bundle.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index df6a3f51006bb..2e4b0d6015936 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -148,21 +148,24 @@ impl BundleInfo { let component_id = *self.component_ids.get_unchecked(bundle_component); let component_status = bundle_status.get_unchecked(bundle_component); match self.storage_types[bundle_component] { - StorageType::Table => match (component_status, overwrite_existing) { - (ComponentStatus::Added, _) => { + StorageType::Table => { + if !matches!( + (component_status, overwrite_existing), + (ComponentStatus::Mutated, ComponentCollision::Skip), + ) { let column = table.get_column(component_id).unwrap(); column.set_unchecked(table_row, component_ptr); let column_status = column.get_ticks_unchecked_mut(table_row); - *column_status = ComponentTicks::new(change_tick); + match component_status { + ComponentStatus::Added => { + *column_status = ComponentTicks::new(change_tick); + } + ComponentStatus::Mutated => { + column_status.set_changed(change_tick); + } + } } - (ComponentStatus::Mutated, ComponentCollision::Overwrite) => { - let column = table.get_column(component_id).unwrap(); - column.set_unchecked(table_row, component_ptr); - let column_status = column.get_ticks_unchecked_mut(table_row); - column_status.set_changed(change_tick); - } - (ComponentStatus::Mutated, ComponentCollision::Skip) => {} - }, + } StorageType::SparseSet => { if matches!(component_status, ComponentStatus::Added) || matches!(overwrite_existing, ComponentCollision::Overwrite) From b74725d09ea378f5feb2c8e2525dc82c3a7be6d6 Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Sun, 2 May 2021 23:07:39 -0400 Subject: [PATCH 7/8] Rename ComponentCollision to CollisionBehaviour --- crates/bevy_ecs/src/bundle.rs | 8 ++++---- crates/bevy_ecs/src/component/mod.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 6 +++--- crates/bevy_ecs/src/world/mod.rs | 12 ++++++------ crates/bevy_ecs/src/world/spawn_batch.rs | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 2e4b0d6015936..0a6ae6bb1cbc4 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -3,7 +3,7 @@ pub use bevy_ecs_macros::Bundle; use crate::{ archetype::ComponentStatus, component::{ - Component, ComponentCollision, ComponentId, ComponentTicks, Components, StorageType, + CollisionBehaviour, Component, ComponentId, ComponentTicks, Components, StorageType, TypeInfo, }, entity::Entity, @@ -138,7 +138,7 @@ impl BundleInfo { bundle_status: &[ComponentStatus], bundle: T, change_tick: u32, - overwrite_existing: ComponentCollision, + overwrite_existing: CollisionBehaviour, ) { // NOTE: get_components calls this closure on each component in "bundle order". // bundle_info.component_ids are also in "bundle order" @@ -151,7 +151,7 @@ impl BundleInfo { StorageType::Table => { if !matches!( (component_status, overwrite_existing), - (ComponentStatus::Mutated, ComponentCollision::Skip), + (ComponentStatus::Mutated, CollisionBehaviour::Skip), ) { let column = table.get_column(component_id).unwrap(); column.set_unchecked(table_row, component_ptr); @@ -168,7 +168,7 @@ impl BundleInfo { } StorageType::SparseSet => { if matches!(component_status, ComponentStatus::Added) - || matches!(overwrite_existing, ComponentCollision::Overwrite) + || matches!(overwrite_existing, CollisionBehaviour::Overwrite) { let sparse_set = sparse_sets.get_mut(component_id).unwrap(); sparse_set.insert(entity, component_ptr, change_tick); diff --git a/crates/bevy_ecs/src/component/mod.rs b/crates/bevy_ecs/src/component/mod.rs index 9c993b13d2e3e..9913a2a7246c3 100644 --- a/crates/bevy_ecs/src/component/mod.rs +++ b/crates/bevy_ecs/src/component/mod.rs @@ -375,7 +375,7 @@ fn check_tick(last_change_tick: &mut u32, change_tick: u32) { /// and [World::write_components](crate::world::World::insert_resource_with_id()) to control /// how collisions between newly inserted and existing component types should be handled. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum ComponentCollision { +pub enum CollisionBehaviour { /// Overwrite existing component/resource of the same type. Overwrite, /// Skip and do not write the new component if it already exists. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 047d0ec26f410..d3b00ae73f9ea 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes, ComponentStatus}, bundle::{Bundle, BundleInfo}, component::{ - Component, ComponentCollision, ComponentId, ComponentTicks, Components, StorageType, + CollisionBehaviour, Component, ComponentId, ComponentTicks, Components, StorageType, }, entity::{Entities, Entity, EntityLocation}, storage::{SparseSet, Storages}, @@ -221,7 +221,7 @@ impl<'w> EntityMut<'w> { bundle_status, bundle, change_tick, - ComponentCollision::Overwrite, + CollisionBehaviour::Overwrite, ) }; self @@ -263,7 +263,7 @@ impl<'w> EntityMut<'w> { bundle_status, bundle, change_tick, - ComponentCollision::Skip, + CollisionBehaviour::Skip, ) }; self diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 5ca5de567513b..908dc26415e38 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -12,7 +12,7 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes}, bundle::{Bundle, Bundles}, component::{ - Component, ComponentCollision, ComponentDescriptor, ComponentId, ComponentTicks, + CollisionBehaviour, Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, ComponentsError, StorageType, }, entity::{Entities, Entity}, @@ -524,7 +524,7 @@ impl World { pub fn insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Overwrite) }; + unsafe { self.insert_resource_with_id(component_id, value, CollisionBehaviour::Overwrite) }; } /// Inserts a new resource with the given `value`. @@ -533,7 +533,7 @@ impl World { pub fn try_insert_resource(&mut self, value: T) { let component_id = self.components.get_or_insert_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Skip) }; + unsafe { self.insert_resource_with_id(component_id, value, CollisionBehaviour::Skip) }; } /// Inserts a new non-send resource with the given `value`. @@ -543,7 +543,7 @@ impl World { self.validate_non_send_access::(); let component_id = self.components.get_or_insert_non_send_resource_id::(); // SAFE: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value, ComponentCollision::Overwrite) }; + unsafe { self.insert_resource_with_id(component_id, value, CollisionBehaviour::Overwrite) }; } /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. @@ -795,7 +795,7 @@ impl World { &mut self, component_id: ComponentId, mut value: T, - overwrite_existing: ComponentCollision, + overwrite_existing: CollisionBehaviour, ) { let change_tick = self.change_tick(); let column = self.initialize_resource_internal(component_id); @@ -809,7 +809,7 @@ impl World { std::mem::forget(value); // SAFE: index was just allocated above *column.get_ticks_unchecked_mut(row) = ComponentTicks::new(change_tick); - } else if matches!(overwrite_existing, ComponentCollision::Overwrite) { + } else if matches!(overwrite_existing, CollisionBehaviour::Overwrite) { // SAFE: column is of type T and has already been allocated *column.get_unchecked(0).cast::() = value; column.get_ticks_unchecked_mut(0).set_changed(change_tick); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 89d36a564ce13..a6730b6624c15 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeId, ComponentStatus}, bundle::{Bundle, BundleInfo}, - component::ComponentCollision, + component::CollisionBehaviour, entity::{Entities, Entity}, storage::{SparseSets, Table}, world::{add_bundle_to_archetype, World}, @@ -105,7 +105,7 @@ where self.bundle_status, bundle, self.change_tick, - ComponentCollision::Overwrite, + CollisionBehaviour::Overwrite, ); self.entities.meta[entity.id as usize].location = location; } From 2e4f5960653198bcf89ba83add702ebe096d2410 Mon Sep 17 00:00:00 2001 From: Garett Cooper Date: Mon, 3 May 2021 17:53:27 -0400 Subject: [PATCH 8/8] Correct string provided for .expect calls on resource access --- crates/bevy_ecs/src/lib.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 06d36aff33172..e113c654fef0d 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -867,20 +867,26 @@ mod tests { .get_archetype_component_id(resource_id) .unwrap(); - assert_eq!(*world.get_resource::().expect("resource exists"), 123); + assert_eq!( + *world.get_resource::().expect("Resource not found"), + 123 + ); assert!(world.contains_resource::()); world.insert_resource(456u64); assert_eq!( - *world.get_resource::().expect("resource exists"), + *world.get_resource::().expect("Resource not found"), 456u64 ); world.insert_resource(789u64); - assert_eq!(*world.get_resource::().expect("resource exists"), 789); + assert_eq!( + *world.get_resource::().expect("Resource not found"), + 789 + ); { - let mut value = world.get_resource_mut::().expect("resource exists"); + let mut value = world.get_resource_mut::().expect("Resource not found"); assert_eq!(*value, 789); *value = 10; } @@ -946,7 +952,7 @@ mod tests { let mut world = World::default(); world.try_insert_resource(64u64); assert_eq!( - *world.get_resource::().expect("Resource exists"), + *world.get_resource::().expect("Resource not found"), 64u64 ); } @@ -957,7 +963,7 @@ mod tests { world.insert_resource(32u64); world.try_insert_resource(64u64); assert_eq!( - *world.get_resource::().expect("Resource exists"), + *world.get_resource::().expect("Resource not found"), 32u64 ); }