From c4847a1a7b28d8bf07bb7803547e7ffa13452dfb Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Wed, 30 Jun 2021 12:00:59 -0500 Subject: [PATCH 1/7] [ecs] Add functionality --- crates/bevy_ecs/src/system/commands.rs | 26 +++++++++++++++++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 90adfb8dc510c..125e8e1b979d6 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -207,6 +207,7 @@ impl<'a, 'b> EntityCommands<'a, 'b> { } /// Adds a single [`Component`] to the current entity. + /// This will overwrite an existing [`Component`] of the same type. /// /// /// # Warning @@ -246,6 +247,16 @@ impl<'a, 'b> EntityCommands<'a, 'b> { self } + /// Adds a single [`Component`] to the current entity, if (and only if) the current entity + /// does not already have a [`Component`] of the same type. + 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 @@ -357,6 +368,21 @@ where } } +#[derive(Debug)] +pub struct TryInsert { + pub entity: Entity, + pub component: T, +} + +impl Command for TryInsert +where + T: Component, +{ + fn write(self: Box, world: &mut World) { + let _ = world.entity_mut(self.entity).try_insert(self.component); + } +} + #[derive(Debug)] pub struct Remove { entity: Entity, diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1815d15798f43..e6f0221bcfc26 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -461,6 +461,15 @@ impl<'w> EntityMut<'w> { entities.meta[self.entity.id as usize].location = new_location; } + pub fn try_insert(&mut self, value: T) -> Result<(), T> { + if self.get::().is_some() { + Err(value) + } else { + self.insert(value); + Ok(()) + } + } + pub fn insert(&mut self, value: T) -> &mut Self { self.insert_bundle((value,)) } From e345ebf26b1f0deee2329ff7d1e5596d7cf166d5 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Wed, 30 Jun 2021 12:25:09 -0500 Subject: [PATCH 2/7] add docs for EntityMut::try_insert --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e6f0221bcfc26..b5b503c680b6d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -461,6 +461,10 @@ impl<'w> EntityMut<'w> { entities.meta[self.entity.id as usize].location = new_location; } + /// Adds a single [`Component`] to the current entity, if (and only if) the current entity + /// does not already have a [`Component`] of the same type. + /// + /// Returns `Ok(())` if the component was added, and `Err(value)` if the componet was not added. pub fn try_insert(&mut self, value: T) -> Result<(), T> { if self.get::().is_some() { Err(value) From c8632e93d6181c311851bb0dd7f25235b4dc6761 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Wed, 30 Jun 2021 12:30:32 -0500 Subject: [PATCH 3/7] update comment and fix typo --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b5b503c680b6d..267a5ed25728d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -464,7 +464,7 @@ impl<'w> EntityMut<'w> { /// Adds a single [`Component`] to the current entity, if (and only if) the current entity /// does not already have a [`Component`] of the same type. /// - /// Returns `Ok(())` if the component was added, and `Err(value)` if the componet was not added. + /// Returns `Ok(())` if the component was inserted, and `Err(value)` if the component was not inserted. pub fn try_insert(&mut self, value: T) -> Result<(), T> { if self.get::().is_some() { Err(value) From 17d5a19a578aec67d34f4a89a315bda08e7c8567 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Fri, 2 Jul 2021 15:55:31 -0500 Subject: [PATCH 4/7] update impl to use an 'entry' like API --- crates/bevy_ecs/src/world/entity_ref.rs | 152 ++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 267a5ed25728d..6a50221af00e0 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -7,7 +7,7 @@ use crate::{ storage::{SparseSet, Storages}, world::{Mut, World}, }; -use std::any::TypeId; +use std::{any::TypeId, marker::PhantomData}; pub struct EntityRef<'w> { world: &'w World, @@ -461,16 +461,18 @@ impl<'w> EntityMut<'w> { entities.meta[self.entity.id as usize].location = new_location; } - /// Adds a single [`Component`] to the current entity, if (and only if) the current entity - /// does not already have a [`Component`] of the same type. - /// - /// Returns `Ok(())` if the component was inserted, and `Err(value)` if the component was not inserted. - pub fn try_insert(&mut self, value: T) -> Result<(), T> { - if self.get::().is_some() { - Err(value) + /// Gets the current entity's [`ComponentEntry`] for in-place manipulation. + pub fn component(&mut self) -> ComponentEntry<'_, 'w, T> { + if let Some(c) = self.get_mut::() { + ComponentEntry::Occupied(OccupiedComponentEntry { + entity: self, + component: c, + }) } else { - self.insert(value); - Ok(()) + ComponentEntry::Vacant(VacantComponentEntry { + entity: self, + _phantom: PhantomData, + }) } } @@ -890,6 +892,136 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { }) } +/// A view into an occupied entity's component entry. +/// It is part of the [`ComponentEntry`] enum. +pub struct OccupiedComponentEntry<'e, 'w, T> { + entity: &'e mut EntityMut<'w>, + component: Mut<'w, T>, +} + +impl<'e, 'w, T> OccupiedComponentEntry<'e, 'w, T> +where + T: Component, +{ + /// Gets a reference to the entity's component. + #[inline] + fn get(&self) -> &T { + self.component.as_ref() + } + + /// Gets a mutable reference to the entity's component. + /// + /// ## Note + /// This triggers change detection. + #[inline] + fn get_mut(&mut self) -> &mut T { + self.component.as_mut() + } + + /// Sets the value of the component, and returns the old component. + #[inline] + fn insert(&mut self, component: T) -> T { + std::mem::replace(self.component.as_mut(), component) + } + + /// Gets a [`Mut`] for the entity's component. + #[inline] + fn into_mut(self) -> Mut<'w, T> { + self.component + } + + /// Removes the entity's component, and returns it. + #[inline] + fn remove(mut self) -> T { + self.entity.remove::().unwrap() + } +} + +/// A view into a vacant entity's component entry. +/// It is part of the [`ComponentEntry`] enum. +pub struct VacantComponentEntry<'e, 'w, T> { + entity: &'e mut EntityMut<'w>, + _phantom: PhantomData, +} + +impl<'e, 'w, T> VacantComponentEntry<'e, 'w, T> +where + T: Component, +{ + /// Inserts the component for the current entity, and returns + /// a [`Mut`] of the component. + #[inline] + fn insert(mut self, component: T) -> Mut<'w, T> { + self.entity.insert(component); + self.entity.get_mut().unwrap() + } +} + +/// A view into a single entity's component in the [`World`], which may either +/// be vacant or occupied. +/// This `enum` is constructed from the [`component`] method on [`EntityMut`] +/// +/// This is analogous to [`entry`] for [`HashMap`] +/// +/// [`entry`]: std::collections::hash_map::Entry +/// [`HashMap`]: std::collections::HashMap +/// [`component`]: EntityMut::component +pub enum ComponentEntry<'e, 'w, T: Component> { + /// An occupied component entry.. + Occupied(OccupiedComponentEntry<'e, 'w, T>), + /// A vacant component entry. + Vacant(VacantComponentEntry<'e, 'w, T>), +} + +impl<'e, 'w, T: Component> ComponentEntry<'e, 'w, T> { + /// Provides in-place mutable access to the current entity's component + /// before any potential inserts into the world. + /// + /// ## Note + /// If the component exists, this triggers change detection. + #[inline] + fn and_modify(self, f: impl FnOnce(&mut T)) -> Self { + match self { + ComponentEntry::Occupied(mut o) => { + f(o.get_mut()); + ComponentEntry::Occupied(o) + } + ComponentEntry::Vacant(v) => ComponentEntry::Vacant(v), + } + } + + /// Ensures a component is in the entry by inserting the default if empty, and returns + /// a [`Mut`] to the component in the entry. + /// + /// The `component` passed to `or_insert` is eagerly evaluated; if you are passing the result of + /// a function call, it is recommended to use [`or_insert_with`], which is lazily evaluated. + /// + /// [`or_insert_with`]: ComponentEntry::or_insert_with + #[inline] + fn or_insert(self, component: T) -> Mut<'w, T> { + self.or_insert_with(|| component) + } + + /// Ensures a component is in the entry by inserting the result of the `default` function + /// if empty, and returns a [`Mut`] to the component in the entry. + #[inline] + fn or_insert_with(self, default: impl FnOnce() -> T) -> Mut<'w, T> { + match self { + ComponentEntry::Occupied(o) => o.component, + ComponentEntry::Vacant(v) => v.insert(default()), + } + } +} + +impl<'e, 'w, T: Component + Default> ComponentEntry<'e, 'w, T> { + /// Ensures a component is in the entry by inserting the default value if empty, and returns + /// a [`Mut`] to the component in the entry. + #[inline] + fn or_default(self) -> Mut<'w, T> { + self.or_insert_with(T::default) + } +} + #[cfg(test)] mod tests { #[test] From a09dfc6b1920f6e88ae2e97bfc892a6ee1fa9fa8 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Fri, 2 Jul 2021 16:00:42 -0500 Subject: [PATCH 5/7] make pub and update commands function --- crates/bevy_ecs/src/system/commands.rs | 5 ++++- crates/bevy_ecs/src/world/entity_ref.rs | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 125e8e1b979d6..e2308fa9db6fa 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -379,7 +379,10 @@ where T: Component, { fn write(self: Box, world: &mut World) { - let _ = world.entity_mut(self.entity).try_insert(self.component); + world + .entity_mut(self.entity) + .component::() + .or_insert(self.component); } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 6a50221af00e0..0f0145bf93bb0 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -905,7 +905,7 @@ where { /// Gets a reference to the entity's component. #[inline] - fn get(&self) -> &T { + pub fn get(&self) -> &T { self.component.as_ref() } @@ -914,25 +914,25 @@ where /// ## Note /// This triggers change detection. #[inline] - fn get_mut(&mut self) -> &mut T { + pub fn get_mut(&mut self) -> &mut T { self.component.as_mut() } /// Sets the value of the component, and returns the old component. #[inline] - fn insert(&mut self, component: T) -> T { + pub fn insert(&mut self, component: T) -> T { std::mem::replace(self.component.as_mut(), component) } /// Gets a [`Mut`] for the entity's component. #[inline] - fn into_mut(self) -> Mut<'w, T> { + pub fn into_mut(self) -> Mut<'w, T> { self.component } /// Removes the entity's component, and returns it. #[inline] - fn remove(mut self) -> T { + pub fn remove(mut self) -> T { self.entity.remove::().unwrap() } } @@ -951,7 +951,7 @@ where /// Inserts the component for the current entity, and returns /// a [`Mut`] of the component. #[inline] - fn insert(mut self, component: T) -> Mut<'w, T> { + pub fn insert(mut self, component: T) -> Mut<'w, T> { self.entity.insert(component); self.entity.get_mut().unwrap() } @@ -980,7 +980,7 @@ impl<'e, 'w, T: Component> ComponentEntry<'e, 'w, T> { /// ## Note /// If the component exists, this triggers change detection. #[inline] - fn and_modify(self, f: impl FnOnce(&mut T)) -> Self { + pub fn and_modify(self, f: impl FnOnce(&mut T)) -> Self { match self { ComponentEntry::Occupied(mut o) => { f(o.get_mut()); @@ -998,14 +998,14 @@ impl<'e, 'w, T: Component> ComponentEntry<'e, 'w, T> { /// /// [`or_insert_with`]: ComponentEntry::or_insert_with #[inline] - fn or_insert(self, component: T) -> Mut<'w, T> { + pub fn or_insert(self, component: T) -> Mut<'w, T> { self.or_insert_with(|| component) } /// Ensures a component is in the entry by inserting the result of the `default` function /// if empty, and returns a [`Mut`] to the component in the entry. #[inline] - fn or_insert_with(self, default: impl FnOnce() -> T) -> Mut<'w, T> { + pub fn or_insert_with(self, default: impl FnOnce() -> T) -> Mut<'w, T> { match self { ComponentEntry::Occupied(o) => o.component, ComponentEntry::Vacant(v) => v.insert(default()), @@ -1017,7 +1017,7 @@ impl<'e, 'w, T: Component + Default> ComponentEntry<'e, 'w, T> { /// Ensures a component is in the entry by inserting the default value if empty, and returns /// a [`Mut`] to the component in the entry. #[inline] - fn or_default(self) -> Mut<'w, T> { + pub fn or_default(self) -> Mut<'w, T> { self.or_insert_with(T::default) } } From 356dc3e8ccced47878eceffd8ad24e1ab2109d46 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Fri, 2 Jul 2021 16:19:44 -0500 Subject: [PATCH 6/7] remove unnecessary mut --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 0f0145bf93bb0..77ea2d8d8bc9d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -932,7 +932,7 @@ where /// Removes the entity's component, and returns it. #[inline] - pub fn remove(mut self) -> T { + pub fn remove(self) -> T { self.entity.remove::().unwrap() } } @@ -951,7 +951,7 @@ where /// Inserts the component for the current entity, and returns /// a [`Mut`] of the component. #[inline] - pub fn insert(mut self, component: T) -> Mut<'w, T> { + pub fn insert(self, component: T) -> Mut<'w, T> { self.entity.insert(component); self.entity.get_mut().unwrap() } From 9665055a1b2581397059669da96a88c1685567b4 Mon Sep 17 00:00:00 2001 From: NathanSWard Date: Fri, 2 Jul 2021 16:28:31 -0500 Subject: [PATCH 7/7] sytle change --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 77ea2d8d8bc9d..7185045c3d581 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -463,10 +463,10 @@ impl<'w> EntityMut<'w> { /// Gets the current entity's [`ComponentEntry`] for in-place manipulation. pub fn component(&mut self) -> ComponentEntry<'_, 'w, T> { - if let Some(c) = self.get_mut::() { + if let Some(component) = self.get_mut::() { ComponentEntry::Occupied(OccupiedComponentEntry { entity: self, - component: c, + component, }) } else { ComponentEntry::Vacant(VacantComponentEntry {