Skip to content
37 changes: 25 additions & 12 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ pub use bevy_ecs_macros::Bundle;

use crate::{
archetype::ComponentStatus,
component::{Component, ComponentId, ComponentTicks, Components, StorageType, TypeInfo},
component::{
CollisionBehaviour, Component, ComponentId, ComponentTicks, Components, StorageType,
TypeInfo,
},
entity::Entity,
storage::{SparseSetIndex, SparseSets, Table},
};
Expand Down Expand Up @@ -135,6 +138,7 @@ impl BundleInfo {
bundle_status: &[ComponentStatus],
bundle: T,
change_tick: u32,
overwrite_existing: CollisionBehaviour,
) {
// NOTE: get_components calls this closure on each component in "bundle order".
// bundle_info.component_ids are also in "bundle order"
Expand All @@ -145,21 +149,30 @@ impl BundleInfo {
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);
if !matches!(
(component_status, overwrite_existing),
(ComponentStatus::Mutated, CollisionBehaviour::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);
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);
if matches!(component_status, ComponentStatus::Added)
|| matches!(overwrite_existing, CollisionBehaviour::Overwrite)
{
let sparse_set = sparse_sets.get_mut(component_id).unwrap();
sparse_set.insert(entity, component_ptr, change_tick);
}
}
}
bundle_component += 1;
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_ecs/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 CollisionBehaviour {
/// Overwrite existing component/resource of the same type.
Overwrite,
/// Skip and do not write the new component if it already exists.
Skip,
}
35 changes: 31 additions & 4 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,20 +867,26 @@ mod tests {
.get_archetype_component_id(resource_id)
.unwrap();

assert_eq!(*world.get_resource::<i32>().expect("resource exists"), 123);
assert_eq!(
*world.get_resource::<i32>().expect("Resource not found"),
123
);
assert!(world.contains_resource::<i32>());

world.insert_resource(456u64);
assert_eq!(
*world.get_resource::<u64>().expect("resource exists"),
*world.get_resource::<u64>().expect("Resource not found"),
456u64
);

world.insert_resource(789u64);
assert_eq!(*world.get_resource::<u64>().expect("resource exists"), 789);
assert_eq!(
*world.get_resource::<u64>().expect("Resource not found"),
789
);

{
let mut value = world.get_resource_mut::<u64>().expect("resource exists");
let mut value = world.get_resource_mut::<u64>().expect("Resource not found");
assert_eq!(*value, 789);
*value = 10;
}
Expand Down Expand Up @@ -941,6 +947,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::<u64>().expect("Resource not found"),
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::<u64>().expect("Resource not found"),
32u64
);
}

#[test]
fn remove_intersection() {
let mut world = World::default();
Expand Down
164 changes: 164 additions & 0 deletions crates/bevy_ecs/src/system/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -206,8 +210,23 @@ impl<'a, 'b> EntityCommands<'a, 'b> {
self
}

/// 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,
bundle,
});
self
}

/// 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
///
Expand Down Expand Up @@ -246,6 +265,25 @@ impl<'a, 'b> EntityCommands<'a, 'b> {
self
}

/// 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,
component,
});
self
}

/// See [`EntityMut::remove_bundle`](crate::world::EntityMut::remove_bundle).
pub fn remove_bundle<T>(&mut self) -> &mut Self
where
Expand Down Expand Up @@ -342,6 +380,20 @@ where
}
}

pub struct TryInsertBundle<T> {
entity: Entity,
bundle: T,
}

impl<T> Command for TryInsertBundle<T>
where
T: Bundle + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
world.entity_mut(self.entity).try_insert_bundle(self.bundle);
}
}

#[derive(Debug)]
pub struct Insert<T> {
pub entity: Entity,
Expand All @@ -357,6 +409,21 @@ where
}
}

#[derive(Debug)]
pub(crate) struct TryInsert<T> {
entity: Entity,
component: T,
}

impl<T> Command for TryInsert<T>
where
T: Component,
{
fn write(self: Box<Self>, world: &mut World) {
world.entity_mut(self.entity).try_insert(self.component);
}
}

#[derive(Debug)]
pub struct Remove<T> {
entity: Entity,
Expand Down Expand Up @@ -451,6 +518,103 @@ mod tests {
assert_eq!(results2, vec![]);
}

#[test]
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::<Vec<_>>();
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::<Vec<_>>();
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)
.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::<Vec<_>>();
assert_eq!(results, vec![(2u32, 4u64)]);
}

#[test]
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)
.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::<Vec<_>>();
assert_eq!(results, vec![(1u32, 2u64)]);
}

#[test]
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)
.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::<Vec<_>>();
assert_eq!(results, vec![(1u32, 4u64)]);
}

#[test]
fn remove_components() {
let mut world = World::default();
Expand Down
Loading