Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an Entry api to EntityWorldMut. #10650

Merged
merged 20 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7dc910a
Added `Entry`, `OccupiedEntry` and `VacantEntry`
13ros27 Nov 19, 2023
d5c0e23
Added Entry.add_modify which uses OccupiedEntry.get_mut, which does l…
13ros27 Nov 19, 2023
17b924e
Added Entry.or_default, using OccupiedEntry.into_mut and VacantEntry.…
13ros27 Nov 19, 2023
808e2b1
Added Entry.insert_entry
13ros27 Nov 20, 2023
35804b0
Added Entry.or_insert
13ros27 Nov 20, 2023
568cfca
Added Entry.or_insert_with
13ros27 Nov 20, 2023
a68f114
Fixed `Entry.insert_entry` so that it will change the value if Occupi…
13ros27 Nov 20, 2023
fa5ed2b
Fixed lifetimes and signatures to avoid the insane lifetime alchemy
13ros27 Nov 20, 2023
215ab4c
Added OccupiedEntry.get and OccupiedEntry.take
13ros27 Nov 20, 2023
1e27ce2
Moved _marker to after entity_world
13ros27 Nov 20, 2023
d936215
Added basic docs (no examples yet)
13ros27 Nov 20, 2023
6636a52
Fixed some doc-links and made these types public so they show up in t…
13ros27 Nov 20, 2023
1ce81c2
Added examples to all of Entry's methods
13ros27 Nov 20, 2023
b7ebc40
Added examples to the methods of OccupiedEntry and VacantEntry
13ros27 Nov 20, 2023
2190952
Appeased clippy
13ros27 Nov 20, 2023
71e0e81
Adjusted doc comment 'default' -> 'given default'
13ros27 Nov 20, 2023
d69e4fa
Add doc test
alice-i-cecile Nov 20, 2023
0c989d8
Fixed doc-test
13ros27 Nov 20, 2023
55c688b
Adjusted doc comment and added an extra comment to `OccupiedEntry` ex…
13ros27 Nov 20, 2023
0ec411c
Replaced unwrap_unchecked with unwrap and changed safety comments to …
13ros27 Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
384 changes: 383 additions & 1 deletion crates/bevy_ecs/src/world/entity_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
};
use bevy_ptr::{OwningPtr, Ptr};
use bevy_utils::tracing::debug;
use std::any::TypeId;
use std::{any::TypeId, marker::PhantomData};

use super::{unsafe_world_cell::UnsafeEntityCell, Ref};

Expand Down Expand Up @@ -1032,6 +1032,388 @@ impl<'w> EntityWorldMut<'w> {
pub fn update_location(&mut self) {
self.location = self.world.entities().get(self.entity).unwrap();
}

/// Gets an Entry into the world for this entity and component for in-place manipulation.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
///
/// The type parameter specifies which component to get.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// let mut world = World::new();
///
/// let mut entity = world.spawn(Comp(0));
/// entity.entry::<Comp>().and_modify(|mut c| c.0 += 1);
/// # assert_eq!(world.query::<&Comp>().single(&world).0, 1);
///
/// let mut new_entity = world.spawn_empty();
/// entity.entry().or_insert_with(|| Comp(4));
/// # assert_eq!(world.query::<&Comp>().single(&world).0, 4);
/// ```
pub fn entry<'a, T: Component>(&'a mut self) -> Entry<'w, 'a, T> {
if self.contains::<T>() {
Entry::Occupied(OccupiedEntry {
entity_world: self,
_marker: PhantomData,
})
} else {
Entry::Vacant(VacantEntry {
entity_world: self,
_marker: PhantomData,
})
}
}
}

/// A view into a single entity and component in a world, which may either be vacant or occupied.
///
/// This `enum` is constructed from the [`entry`] method on [`EntityWorldMut`].
13ros27 marked this conversation as resolved.
Show resolved Hide resolved
///
/// [`entry`]: EntityWorldMut::entry
pub enum Entry<'w, 'a, T: Component> {
/// An occupied entry.
Occupied(OccupiedEntry<'w, 'a, T>),
/// A vacant entry.
Vacant(VacantEntry<'w, 'a, T>),
}

impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
/// Provides in-place mutable access to an occupied entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(0));
///
/// entity.entry::<Comp>().and_modify(|mut c| c.0 += 1);
/// assert_eq!(world.query::<&Comp>().single(&world).0, 1);
/// ```
#[inline]
pub fn and_modify<F: FnOnce(Mut<'_, T>)>(self, f: F) -> Self {
match self {
Entry::Occupied(mut entry) => {
f(entry.get_mut());
Entry::Occupied(entry)
}
Entry::Vacant(entry) => Entry::Vacant(entry),
}
}

/// Replaces the component of the entry, and returns an [`OccupiedEntry`].
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// let entry = entity.entry().insert_entry(Comp(4));
/// assert_eq!(entry.get(), &Comp(4));
///
/// let entry = entity.entry().insert_entry(Comp(2));
/// assert_eq!(entry.get(), &Comp(2));
/// ```
#[inline]
pub fn insert_entry(self, component: T) -> OccupiedEntry<'w, 'a, T> {
match self {
Entry::Occupied(mut entry) => {
entry.insert(component);
entry
}
Entry::Vacant(entry) => entry.insert_entry(component),
}
}

/// Ensures the entry has this component by inserting the given default if empty, and
/// returns a mutable reference to this component in the entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// entity.entry().or_insert(Comp(4));
/// # let entity_id = entity.id();
/// assert_eq!(world.query::<&Comp>().single(&world).0, 4);
///
/// # let mut entity = world.get_entity_mut(entity_id).unwrap();
/// entity.entry().or_insert(Comp(15)).0 *= 2;
/// assert_eq!(world.query::<&Comp>().single(&world).0, 8);
/// ```
#[inline]
pub fn or_insert(self, default: T) -> Mut<'a, T> {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(default),
}
}

/// Ensures the entry has this component by inserting the result of the default function if
/// empty, and returns a mutable reference to this component in the entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// entity.entry().or_insert_with(|| Comp(4));
/// assert_eq!(world.query::<&Comp>().single(&world).0, 4);
/// ```
#[inline]
pub fn or_insert_with<F: FnOnce() -> T>(self, default: F) -> Mut<'a, T> {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(default()),
}
}
}

impl<'w, 'a, T: Component + Default> Entry<'w, 'a, T> {
/// Ensures the entry has this component by inserting the default value if empty, and
/// returns a mutable reference to this component in the entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// entity.entry::<Comp>().or_default();
/// assert_eq!(world.query::<&Comp>().single(&world).0, 0);
/// ```
#[inline]
pub fn or_default(self) -> Mut<'a, T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to make both a FromReflect and FromWorld equivalent, but those can wait.

match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(Default::default()),
}
}
}

/// A view into an occupied entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum.
pub struct OccupiedEntry<'w, 'a, T: Component> {
entity_world: &'a mut EntityWorldMut<'w>,
_marker: PhantomData<T>,
}

impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
/// Gets a reference to the component in the entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(5));
///
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
/// assert_eq!(o.get().0, 5);
/// }
/// ```
#[inline]
pub fn get(&self) -> &T {
// SAFETY: If we have an OccupiedEntry the component must exist.
13ros27 marked this conversation as resolved.
Show resolved Hide resolved
unsafe { self.entity_world.get::<T>().unwrap_unchecked() }
}

/// Gets a mutable reference to the component in the entry.
///
/// If you need a reference to the `OccupiedEntry` which may outlive the destruction of
/// the `Entry` value, see [`into_mut`].
///
/// [`into_mut`]: Self::into_mut
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(5));
///
/// if let Entry::Occupied(mut o) = entity.entry::<Comp>() {
/// o.get_mut().0 += 10;
/// assert_eq!(o.get().0, 15);
///
/// // We can use the same Entry multiple times.
/// o.get_mut().0 += 2
/// }
///
/// assert_eq!(world.query::<&Comp>().single(&world).0, 17);
/// ```
#[inline]
pub fn get_mut(&mut self) -> Mut<'_, T> {
// SAFETY: If we have an OccupiedEntry the component must exist.
unsafe { self.entity_world.get_mut::<T>().unwrap_unchecked() }
}

/// Converts the `OccupiedEntry` into a mutable reference to the value in the entry with
/// a lifetime bound to the `EntityWorldMut`.
///
/// If you need multiple references to the `OccupiedEntry`, see [`get_mut`].
///
/// [`get_mut`]: Self::get_mut
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(5));
///
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
/// o.into_mut().0 += 10;
/// }
///
/// assert_eq!(world.query::<&Comp>().single(&world).0, 15);
/// ```
#[inline]
pub fn into_mut(self) -> Mut<'a, T> {
// SAFETY: If we have an OccupiedEntry the component must exist.
unsafe { self.entity_world.get_mut().unwrap_unchecked() }
}

/// Replaces the component of the entry.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(5));
///
/// if let Entry::Occupied(mut o) = entity.entry::<Comp>() {
/// o.insert(Comp(10));
/// }
///
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
/// ```
#[inline]
pub fn insert(&mut self, component: T) {
self.entity_world.insert(component);
}

/// Removes the component from the entry and returns it.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn(Comp(5));
///
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
/// assert_eq!(o.take(), Comp(5));
/// }
///
/// assert_eq!(world.query::<&Comp>().iter(&world).len(), 0);
/// ```
#[inline]
pub fn take(self) -> T {
// SAFETY: If we have an OccupiedEntry the component must exist.
unsafe { self.entity_world.take().unwrap_unchecked() }
}
}

/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum.
pub struct VacantEntry<'w, 'a, T: Component> {
entity_world: &'a mut EntityWorldMut<'w>,
_marker: PhantomData<T>,
}

impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
/// Inserts the component into the `VacantEntry` and returns a mutable reference to it.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// if let Entry::Vacant(v) = entity.entry::<Comp>() {
/// v.insert(Comp(10));
/// }
///
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
/// ```
#[inline]
pub fn insert(self, component: T) -> Mut<'a, T> {
self.entity_world.insert(component);
// SAFETY: We just added this component.
unsafe { self.entity_world.get_mut::<T>().unwrap_unchecked() }
}

/// Inserts the component into the `VacantEntry` and returns an `OccupiedEntry`.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, world::Entry};
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
/// struct Comp(u32);
///
/// # let mut world = World::new();
/// let mut entity = world.spawn_empty();
///
/// if let Entry::Vacant(v) = entity.entry::<Comp>() {
/// v.insert_entry(Comp(10));
/// }
///
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
/// ```
#[inline]
pub fn insert_entry(self, component: T) -> OccupiedEntry<'w, 'a, T> {
self.entity_world.insert(component);
OccupiedEntry {
entity_world: self.entity_world,
_marker: PhantomData,
}
}
}

/// Inserts a dynamic [`Bundle`] into the entity.
Expand Down
Loading
Loading