# Objective - Fixes #16208 ## Solution - Added an associated type to `Component`, `Mutability`, which flags whether a component is mutable, or immutable. If `Mutability= Mutable`, the component is mutable. If `Mutability= Immutable`, the component is immutable. - Updated `derive_component` to default to mutable unless an `#[component(immutable)]` attribute is added. - Updated `ReflectComponent` to check if a component is mutable and, if not, panic when attempting to mutate. ## Testing - CI - `immutable_components` example. --- ## Showcase Users can now mark a component as `#[component(immutable)]` to prevent safe mutation of a component while it is attached to an entity: ```rust #[derive(Component)] #[component(immutable)] struct Foo { // ... } ``` This prevents creating an exclusive reference to the component while it is attached to an entity. This is particularly powerful when combined with component hooks, as you can now fully track a component's value, ensuring whatever invariants you desire are upheld. Before this would be done my making a component private, and manually creating a `QueryData` implementation which only permitted read access. <details> <summary>Using immutable components as an index</summary> ```rust /// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component)] #[component( immutable, on_insert = on_insert_name, on_replace = on_replace_name, )] pub struct Name(pub &'static str); /// This index allows for O(1) lookups of an [`Entity`] by its [`Name`]. #[derive(Resource, Default)] struct NameIndex { name_to_entity: HashMap<Name, Entity>, } impl NameIndex { fn get_entity(&self, name: &'static str) -> Option<Entity> { self.name_to_entity.get(&Name(name)).copied() } } fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { let Some(&name) = world.entity(entity).get::<Name>() else { unreachable!() }; let Some(mut index) = world.get_resource_mut::<NameIndex>() else { return; }; index.name_to_entity.insert(name, entity); } fn on_replace_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { let Some(&name) = world.entity(entity).get::<Name>() else { unreachable!() }; let Some(mut index) = world.get_resource_mut::<NameIndex>() else { return; }; index.name_to_entity.remove(&name); } // Setup our name index world.init_resource::<NameIndex>(); // Spawn some entities! let alyssa = world.spawn(Name("Alyssa")).id(); let javier = world.spawn(Name("Javier")).id(); // Check our index let index = world.resource::<NameIndex>(); assert_eq!(index.get_entity("Alyssa"), Some(alyssa)); assert_eq!(index.get_entity("Javier"), Some(javier)); // Changing the name of an entity is also fully capture by our index world.entity_mut(javier).insert(Name("Steven")); // Javier changed their name to Steven let steven = javier; // Check our index let index = world.resource::<NameIndex>(); assert_eq!(index.get_entity("Javier"), None); assert_eq!(index.get_entity("Steven"), Some(steven)); ``` </details> Additionally, users can use `Component<Mutability = ...>` in trait bounds to enforce that a component _is_ mutable or _is_ immutable. When using `Component` as a trait bound without specifying `Mutability`, any component is applicable. However, methods which only work on mutable or immutable components are unavailable, since the compiler must be pessimistic about the type. ## Migration Guide - When implementing `Component` manually, you must now provide a type for `Mutability`. The type `Mutable` provides equivalent behaviour to earlier versions of `Component`: ```rust impl Component for Foo { type Mutability = Mutable; // ... } ``` - When working with generic components, you may need to specify that your generic parameter implements `Component<Mutability = Mutable>` rather than `Component` if you require mutable access to said component. - The entity entry API has had to have some changes made to minimise friction when working with immutable components. Methods which previously returned a `Mut<T>` will now typically return an `OccupiedEntry<T>` instead, requiring you to add an `into_mut()` to get the `Mut<T>` item again. ## Draft Release Notes Components can now be made immutable while stored within the ECS. Components are the fundamental unit of data within an ECS, and Bevy provides a number of ways to work with them that align with Rust's rules around ownership and borrowing. One part of this is hooks, which allow for defining custom behavior at key points in a component's lifecycle, such as addition and removal. However, there is currently no way to respond to _mutation_ of a component using hooks. The reasons for this are quite technical, but to summarize, their addition poses a significant challenge to Bevy's core promises around performance. Without mutation hooks, it's relatively trivial to modify a component in such a way that breaks invariants it intends to uphold. For example, you can use `core::mem::swap` to swap the components of two entities, bypassing the insertion and removal hooks. This means the only way to react to this modification is via change detection in a system, which then begs the question of what happens _between_ that alteration and the next run of that system? Alternatively, you could make your component private to prevent mutation, but now you need to provide commands and a custom `QueryData` implementation to allow users to interact with your component at all. Immutable components solve this problem by preventing the creation of an exclusive reference to the component entirely. Without an exclusive reference, the only way to modify an immutable component is via removal or replacement, which is fully captured by component hooks. To make a component immutable, simply add `#[component(immutable)]`: ```rust #[derive(Component)] #[component(immutable)] struct Foo { // ... } ``` When implementing `Component` manually, there is an associated type `Mutability` which controls this behavior: ```rust impl Component for Foo { type Mutability = Mutable; // ... } ``` Note that this means when working with generic components, you may need to specify that a component is mutable to gain access to certain methods: ```rust // Before fn bar<C: Component>() { // ... } // After fn bar<C: Component<Mutability = Mutable>>() { // ... } ``` With this new tool, creating index components, or caching data on an entity should be more user friendly, allowing libraries to provide APIs relying on components and hooks to uphold their invariants. ## Notes - ~~I've done my best to implement this feature, but I'm not happy with how reflection has turned out. If any reflection SMEs know a way to improve this situation I'd greatly appreciate it.~~ There is an outstanding issue around the fallibility of mutable methods on `ReflectComponent`, but the DX is largely unchanged from `main` now. - I've attempted to prevent all safe mutable access to a component that does not implement `Component<Mutability = Mutable>`, but there may still be some methods I have missed. Please indicate so and I will address them, as they are bugs. - Unsafe is an escape hatch I am _not_ attempting to prevent. Whatever you do with unsafe is between you and your compiler. - I am marking this PR as ready, but I suspect it will undergo fairly major revisions based on SME feedback. - I've marked this PR as _Uncontroversial_ based on the feature, not the implementation. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Co-authored-by: Nuutti Kotivuori <naked@iki.fi>
178 lines
5.8 KiB
Rust
178 lines
5.8 KiB
Rust
#[cfg(feature = "reflect")]
|
|
use bevy_ecs::reflect::{
|
|
ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities,
|
|
ReflectVisitEntitiesMut,
|
|
};
|
|
use bevy_ecs::{
|
|
component::{Component, ComponentCloneHandler, Mutable, StorageType},
|
|
entity::{Entity, VisitEntitiesMut},
|
|
prelude::FromWorld,
|
|
world::World,
|
|
};
|
|
use core::{ops::Deref, slice};
|
|
use smallvec::SmallVec;
|
|
|
|
/// Contains references to the child entities of this entity.
|
|
///
|
|
/// Each child must contain a [`Parent`] component that points back to this entity.
|
|
/// This component rarely needs to be created manually,
|
|
/// consider using higher level utilities like [`BuildChildren::with_children`]
|
|
/// which are safer and easier to use.
|
|
///
|
|
/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`].
|
|
///
|
|
/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt
|
|
/// [`Query`]: bevy_ecs::system::Query
|
|
/// [`Parent`]: crate::components::parent::Parent
|
|
/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children
|
|
#[derive(Debug, VisitEntitiesMut)]
|
|
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
|
|
#[cfg_attr(
|
|
feature = "reflect",
|
|
reflect(
|
|
Component,
|
|
MapEntities,
|
|
VisitEntities,
|
|
VisitEntitiesMut,
|
|
Debug,
|
|
FromWorld
|
|
)
|
|
)]
|
|
pub struct Children(pub(crate) SmallVec<[Entity; 8]>);
|
|
|
|
impl Component for Children {
|
|
const STORAGE_TYPE: StorageType = StorageType::Table;
|
|
type Mutability = Mutable;
|
|
|
|
fn get_component_clone_handler() -> ComponentCloneHandler {
|
|
ComponentCloneHandler::Ignore
|
|
}
|
|
}
|
|
|
|
// TODO: We need to impl either FromWorld or Default so Children can be registered as Reflect.
|
|
// This is because Reflect deserialize by creating an instance and apply a patch on top.
|
|
// However Children should only ever be set with a real user-defined entities. Its worth looking
|
|
// into better ways to handle cases like this.
|
|
impl FromWorld for Children {
|
|
#[inline]
|
|
fn from_world(_world: &mut World) -> Self {
|
|
Children(SmallVec::new())
|
|
}
|
|
}
|
|
|
|
impl Children {
|
|
/// Constructs a [`Children`] component with the given entities.
|
|
#[inline]
|
|
pub(crate) fn from_entities(entities: &[Entity]) -> Self {
|
|
Self(SmallVec::from_slice(entities))
|
|
}
|
|
|
|
/// Swaps the child at `a_index` with the child at `b_index`.
|
|
#[inline]
|
|
pub fn swap(&mut self, a_index: usize, b_index: usize) {
|
|
self.0.swap(a_index, b_index);
|
|
}
|
|
|
|
/// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
|
|
/// in place using the provided comparator function.
|
|
///
|
|
/// For the underlying implementation, see [`slice::sort_by`].
|
|
///
|
|
/// For the unstable version, see [`sort_unstable_by`](Children::sort_unstable_by).
|
|
///
|
|
/// See also [`sort_by_key`](Children::sort_by_key), [`sort_by_cached_key`](Children::sort_by_cached_key).
|
|
#[inline]
|
|
pub fn sort_by<F>(&mut self, compare: F)
|
|
where
|
|
F: FnMut(&Entity, &Entity) -> core::cmp::Ordering,
|
|
{
|
|
self.0.sort_by(compare);
|
|
}
|
|
|
|
/// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
|
|
/// in place using the provided key extraction function.
|
|
///
|
|
/// For the underlying implementation, see [`slice::sort_by_key`].
|
|
///
|
|
/// For the unstable version, see [`sort_unstable_by_key`](Children::sort_unstable_by_key).
|
|
///
|
|
/// See also [`sort_by`](Children::sort_by), [`sort_by_cached_key`](Children::sort_by_cached_key).
|
|
#[inline]
|
|
pub fn sort_by_key<K, F>(&mut self, compare: F)
|
|
where
|
|
F: FnMut(&Entity) -> K,
|
|
K: Ord,
|
|
{
|
|
self.0.sort_by_key(compare);
|
|
}
|
|
|
|
/// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
|
|
/// in place using the provided key extraction function. Only evaluates each key at most
|
|
/// once per sort, caching the intermediate results in memory.
|
|
///
|
|
/// For the underlying implementation, see [`slice::sort_by_cached_key`].
|
|
///
|
|
/// See also [`sort_by`](Children::sort_by), [`sort_by_key`](Children::sort_by_key).
|
|
#[inline]
|
|
pub fn sort_by_cached_key<K, F>(&mut self, compare: F)
|
|
where
|
|
F: FnMut(&Entity) -> K,
|
|
K: Ord,
|
|
{
|
|
self.0.sort_by_cached_key(compare);
|
|
}
|
|
|
|
/// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
|
|
/// in place using the provided comparator function.
|
|
///
|
|
/// For the underlying implementation, see [`slice::sort_unstable_by`].
|
|
///
|
|
/// For the stable version, see [`sort_by`](Children::sort_by).
|
|
///
|
|
/// See also [`sort_unstable_by_key`](Children::sort_unstable_by_key).
|
|
#[inline]
|
|
pub fn sort_unstable_by<F>(&mut self, compare: F)
|
|
where
|
|
F: FnMut(&Entity, &Entity) -> core::cmp::Ordering,
|
|
{
|
|
self.0.sort_unstable_by(compare);
|
|
}
|
|
|
|
/// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability)
|
|
/// in place using the provided key extraction function.
|
|
///
|
|
/// For the underlying implementation, see [`slice::sort_unstable_by_key`].
|
|
///
|
|
/// For the stable version, see [`sort_by_key`](Children::sort_by_key).
|
|
///
|
|
/// See also [`sort_unstable_by`](Children::sort_unstable_by).
|
|
#[inline]
|
|
pub fn sort_unstable_by_key<K, F>(&mut self, compare: F)
|
|
where
|
|
F: FnMut(&Entity) -> K,
|
|
K: Ord,
|
|
{
|
|
self.0.sort_unstable_by_key(compare);
|
|
}
|
|
}
|
|
|
|
impl Deref for Children {
|
|
type Target = [Entity];
|
|
|
|
#[inline(always)]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0[..]
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for &'a Children {
|
|
type Item = <Self::IntoIter as Iterator>::Item;
|
|
|
|
type IntoIter = slice::Iter<'a, Entity>;
|
|
|
|
#[inline(always)]
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.iter()
|
|
}
|
|
}
|