Add Immutable Component Support (#16372)

# 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>
This commit is contained in:
Zachary Harrold 2024-12-06 01:27:48 +11:00 committed by GitHub
parent b7bcd313ca
commit a35811d088
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 598 additions and 145 deletions

View file

@ -1954,6 +1954,17 @@ description = "Creates a hierarchy of parents and children entities"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "immutable_components"
path = "examples/ecs/immutable_components.rs"
doc-scrape-examples = true
[package.metadata.example.immutable_components]
name = "Immutable Components"
description = "Demonstrates the creation and utility of immutable components"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "iter_combinations"
path = "examples/ecs/iter_combinations.rs"

View file

@ -1,5 +1,5 @@
use bevy_ecs::{
component::Component,
component::{Component, Mutable},
entity::Entity,
prelude::{Added, Changed, EntityWorldMut, QueryState},
query::QueryFilter,
@ -124,7 +124,7 @@ fn all_added_detection(criterion: &mut Criterion) {
}
}
fn all_changed_detection_generic<T: Component + Default + BenchModify>(
fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
group: &mut BenchGroup,
entity_count: u32,
) {
@ -172,7 +172,7 @@ fn all_changed_detection(criterion: &mut Criterion) {
}
}
fn few_changed_detection_generic<T: Component + Default + BenchModify>(
fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
group: &mut BenchGroup,
entity_count: u32,
) {
@ -222,7 +222,7 @@ fn few_changed_detection(criterion: &mut Criterion) {
}
}
fn none_changed_detection_generic<T: Component + Default>(
fn none_changed_detection_generic<T: Component<Mutability = Mutable> + Default>(
group: &mut BenchGroup,
entity_count: u32,
) {
@ -271,7 +271,7 @@ fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
}
}
fn add_archetypes_entities<T: Component + Default>(
fn add_archetypes_entities<T: Component<Mutability = Mutable> + Default>(
world: &mut World,
archetype_count: u16,
entity_count: u32,
@ -298,7 +298,7 @@ fn add_archetypes_entities<T: Component + Default>(
}
}
}
fn multiple_archetype_none_changed_detection_generic<T: Component + Default + BenchModify>(
fn multiple_archetype_none_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
group: &mut BenchGroup,
archetype_count: u16,
entity_count: u32,

View file

@ -89,7 +89,7 @@ use core::{
marker::PhantomData,
};
use bevy_ecs::component::Component;
use bevy_ecs::component::{Component, Mutable};
use bevy_math::curve::{
cores::{UnevenCore, UnevenCoreError},
iterable::IterableCurve,
@ -221,7 +221,7 @@ pub struct AnimatedField<C, A, F: Fn(&mut C) -> &mut A> {
impl<C, A, F> AnimatableProperty for AnimatedField<C, A, F>
where
C: Component,
C: Component<Mutability = Mutable>,
A: Animatable + Clone + Sync + Debug,
F: Fn(&mut C) -> &mut A + Send + Sync + 'static,
{

View file

@ -68,6 +68,7 @@ impl Default for OrderIndependentTransparencySettings {
// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
impl Component for OrderIndependentTransparencySettings {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|world, entity, _| {

View file

@ -32,6 +32,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet;
type Mutability = #bevy_ecs_path::component::Mutable;
}
})
}
@ -127,11 +128,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let mutable_type = attrs
.immutable
.then_some(quote! { #bevy_ecs_path::component::Immutable })
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
type Mutability = #mutable_type;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::Components,
@ -191,6 +198,8 @@ pub const ON_INSERT: &str = "on_insert";
pub const ON_REPLACE: &str = "on_replace";
pub const ON_REMOVE: &str = "on_remove";
pub const IMMUTABLE: &str = "immutable";
struct Attrs {
storage: StorageTy,
requires: Option<Punctuated<Require, Comma>>,
@ -198,6 +207,7 @@ struct Attrs {
on_insert: Option<ExprPath>,
on_replace: Option<ExprPath>,
on_remove: Option<ExprPath>,
immutable: bool,
}
#[derive(Clone, Copy)]
@ -228,6 +238,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
on_replace: None,
on_remove: None,
requires: None,
immutable: false,
};
let mut require_paths = HashSet::new();
@ -257,6 +268,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
} else if nested.path.is_ident(ON_REMOVE) {
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
Ok(())
} else if nested.path.is_ident(IMMUTABLE) {
attrs.immutable = true;
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}

View file

@ -75,12 +75,18 @@ pub use bevy_ecs_macros::require;
///
/// # Component and data access
///
/// Components can be marked as immutable by adding the `#[component(immutable)]`
/// attribute when using the derive macro.
/// See the documentation for [`ComponentMutability`] for more details around this
/// feature.
///
/// See the [`entity`] module level documentation to learn how to add or remove components from an entity.
///
/// See the documentation for [`Query`] to learn how to access component data from a system.
///
/// [`entity`]: crate::entity#usage
/// [`Query`]: crate::system::Query
/// [`ComponentMutability`]: crate::component::ComponentMutability
///
/// # Choosing a storage type
///
@ -380,6 +386,14 @@ pub trait Component: Send + Sync + 'static {
/// A constant indicating the storage type used for this component.
const STORAGE_TYPE: StorageType;
/// A marker type to assist Bevy with determining if this component is
/// mutable, or immutable. Mutable components will have [`Component<Mutability = Mutable>`],
/// while immutable components will instead have [`Component<Mutability = Immutable>`].
///
/// * For a component to be mutable, this type must be [`Mutable`].
/// * For a component to be immutable, this type must be [`Immutable`].
type Mutability: ComponentMutability;
/// Called when registering this component, allowing mutable access to its [`ComponentHooks`].
fn register_component_hooks(_hooks: &mut ComponentHooks) {}
@ -401,6 +415,61 @@ pub trait Component: Send + Sync + 'static {
}
}
mod private {
pub trait Seal {}
}
/// The mutability option for a [`Component`]. This can either be:
/// * [`Mutable`]
/// * [`Immutable`]
///
/// This is controlled through either [`Component::Mutability`] or `#[component(immutable)]`
/// when using the derive macro.
///
/// Immutable components are guaranteed to never have an exclusive reference,
/// `&mut ...`, created while inserted onto an entity.
/// In all other ways, they are identical to mutable components.
/// This restriction allows hooks to observe all changes made to an immutable
/// component, effectively turning the `OnInsert` and `OnReplace` hooks into a
/// `OnMutate` hook.
/// This is not practical for mutable components, as the runtime cost of invoking
/// a hook for every exclusive reference created would be far too high.
///
/// # Examples
///
/// ```rust
/// # use bevy_ecs::component::Component;
/// #
/// #[derive(Component)]
/// #[component(immutable)]
/// struct ImmutableFoo;
/// ```
pub trait ComponentMutability: private::Seal + 'static {
/// Boolean to indicate if this mutability setting implies a mutable or immutable
/// component.
const MUTABLE: bool;
}
/// Parameter indicating a [`Component`] is immutable.
///
/// See [`ComponentMutability`] for details.
pub struct Immutable;
impl private::Seal for Immutable {}
impl ComponentMutability for Immutable {
const MUTABLE: bool = false;
}
/// Parameter indicating a [`Component`] is mutable.
///
/// See [`ComponentMutability`] for details.
pub struct Mutable;
impl private::Seal for Mutable {}
impl ComponentMutability for Mutable {
const MUTABLE: bool = true;
}
/// The storage used for a specific component type.
///
/// # Examples
@ -626,6 +695,12 @@ impl ComponentInfo {
&self.descriptor.name
}
/// Returns `true` if the current component is mutable.
#[inline]
pub fn mutable(&self) -> bool {
self.descriptor.mutable
}
/// Returns the [`TypeId`] of the underlying component type.
/// Returns `None` if the component does not correspond to a Rust type.
#[inline]
@ -778,6 +853,7 @@ pub struct ComponentDescriptor {
// this descriptor describes.
// None if the underlying type doesn't need to be dropped
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
mutable: bool,
}
// We need to ignore the `drop` field in our `Debug` impl
@ -789,6 +865,7 @@ impl Debug for ComponentDescriptor {
.field("is_send_and_sync", &self.is_send_and_sync)
.field("type_id", &self.type_id)
.field("layout", &self.layout)
.field("mutable", &self.mutable)
.finish()
}
}
@ -813,6 +890,7 @@ impl ComponentDescriptor {
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
mutable: T::Mutability::MUTABLE,
}
}
@ -826,6 +904,7 @@ impl ComponentDescriptor {
storage_type: StorageType,
layout: Layout,
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
mutable: bool,
) -> Self {
Self {
name: name.into(),
@ -834,6 +913,7 @@ impl ComponentDescriptor {
type_id: None,
layout,
drop,
mutable,
}
}
@ -850,6 +930,7 @@ impl ComponentDescriptor {
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
mutable: true,
}
}
@ -861,6 +942,7 @@ impl ComponentDescriptor {
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
mutable: true,
}
}
@ -882,6 +964,12 @@ impl ComponentDescriptor {
pub fn name(&self) -> &str {
self.name.as_ref()
}
/// Returns whether this component is mutable.
#[inline]
pub fn mutable(&self) -> bool {
self.mutable
}
}
/// Function type that can be used to clone an entity.

View file

@ -118,12 +118,13 @@ impl EntityCloner {
/// Here's an example of how to do it using [`get_component_clone_handler`](Component::get_component_clone_handler):
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler};
/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler, Mutable};
/// #[derive(Clone)]
/// struct SomeComponent;
///
/// impl Component for SomeComponent {
/// const STORAGE_TYPE: StorageType = StorageType::Table;
/// type Mutability = Mutable;
/// fn get_component_clone_handler() -> ComponentCloneHandler {
/// ComponentCloneHandler::Custom(component_clone_via_clone::<Self>)
/// }

View file

@ -1,5 +1,5 @@
use crate::{
component::{Component, ComponentCloneHandler, ComponentHooks, StorageType},
component::{Component, ComponentCloneHandler, ComponentHooks, Mutable, StorageType},
entity::{Entity, EntityCloneBuilder, EntityCloner},
observer::ObserverState,
world::{DeferredWorld, World},
@ -11,6 +11,7 @@ pub(crate) struct ObservedBy(pub(crate) Vec<Entity>);
impl Component for ObservedBy {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_remove(|mut world, entity, _| {

View file

@ -456,7 +456,7 @@ impl World {
// Populate ObservedBy for each observed entity.
for watched_entity in &(*observer_state).descriptor.entities {
let mut entity_mut = self.entity_mut(*watched_entity);
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default();
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default().into_mut();
observed_by.0.push(observer_entity);
}
(&*observer_state, &mut self.archetypes, &mut self.observers)

View file

@ -1,7 +1,7 @@
use core::any::Any;
use crate::{
component::{ComponentHook, ComponentHooks, ComponentId, StorageType},
component::{ComponentHook, ComponentHooks, ComponentId, Mutable, StorageType},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
@ -62,6 +62,7 @@ impl ObserverState {
impl Component for ObserverState {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, entity, _| {
@ -314,6 +315,7 @@ impl Observer {
impl Component for Observer {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|world, entity, _id| {
let Some(observe) = world.get::<Self>(entity) else {

View file

@ -2,7 +2,7 @@ use crate::{
archetype::{Archetype, Archetypes},
bundle::Bundle,
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
component::{Component, ComponentId, Components, StorageType, Tick},
component::{Component, ComponentId, Components, Mutable, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow},
@ -1608,7 +1608,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
}
/// SAFETY: access of `&T` is a subset of `&mut T`
unsafe impl<'__w, T: Component> QueryData for &'__w mut T {
unsafe impl<'__w, T: Component<Mutability = Mutable>> QueryData for &'__w mut T {
type ReadOnly = &'__w T;
}

View file

@ -60,14 +60,16 @@
use super::from_reflect_with_fallback;
use crate::{
change_detection::Mut,
component::{Component, ComponentId},
component::{ComponentId, ComponentMutability},
entity::Entity,
prelude::Component,
world::{
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
FilteredEntityRef, World,
},
};
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry};
use disqualified::ShortName;
/// A struct used to operate on reflected [`Component`] trait of a type.
///
@ -150,11 +152,17 @@ impl ReflectComponent {
/// # Panics
///
/// Panics if there is no [`Component`] of the given type.
///
/// Will also panic if [`Component`] is immutable.
pub fn apply<'a>(&self, entity: impl Into<EntityMut<'a>>, component: &dyn PartialReflect) {
(self.0.apply)(entity.into(), component);
}
/// Uses reflection to set the value of this [`Component`] type in the entity to the given value or insert a new one if it does not exist.
///
/// # Panics
///
/// Panics if [`Component`] is immutable.
pub fn apply_or_insert(
&self,
entity: &mut EntityWorldMut,
@ -180,6 +188,10 @@ impl ReflectComponent {
}
/// Gets the value of this [`Component`] type from the entity as a mutable reflected reference.
///
/// # Panics
///
/// Panics if [`Component`] is immutable.
pub fn reflect_mut<'a>(
&self,
entity: impl Into<FilteredEntityMut<'a>>,
@ -192,6 +204,10 @@ impl ReflectComponent {
/// violating Rust's aliasing rules. To avoid this:
/// * Only call this method with a [`UnsafeEntityCell`] that may be used to mutably access the component on the entity `entity`
/// * Don't call this method more than once in the same scope for a given [`Component`].
///
/// # Panics
///
/// Panics if [`Component`] is immutable.
pub unsafe fn reflect_unchecked_mut<'a>(
&self,
entity: UnsafeEntityCell<'a>,
@ -265,6 +281,8 @@ impl ReflectComponent {
impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
fn from_type() -> Self {
// TODO: Currently we panic if a component is immutable and you use
// reflection to mutate it. Perhaps the mutation methods should be fallible?
ReflectComponent(ReflectComponentFns {
insert: |entity, reflected_component, registry| {
let component = entity.world_scope(|world| {
@ -273,11 +291,23 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
entity.insert(component);
},
apply: |mut entity, reflected_component| {
let mut component = entity.get_mut::<C>().unwrap();
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
panic!("Cannot call `ReflectComponent::apply` on component {name}. It is immutable, and cannot modified through reflection");
}
// SAFETY: guard ensures `C` is a mutable component
let mut component = unsafe { entity.get_mut_assume_mutable::<C>() }.unwrap();
component.apply(reflected_component);
},
apply_or_insert: |entity, reflected_component, registry| {
if let Some(mut component) = entity.get_mut::<C>() {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
panic!("Cannot call `ReflectComponent::apply_or_insert` on component {name}. It is immutable, and cannot modified through reflection");
}
// SAFETY: guard ensures `C` is a mutable component
if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::<C>() } {
component.apply(reflected_component.as_partial_reflect());
} else {
let component = entity.world_scope(|world| {
@ -300,14 +330,28 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
},
reflect: |entity| entity.get::<C>().map(|c| c as &dyn Reflect),
reflect_mut: |entity| {
entity
.into_mut::<C>()
.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
panic!("Cannot call `ReflectComponent::reflect_mut` on component {name}. It is immutable, and cannot modified through reflection");
}
// SAFETY: guard ensures `C` is a mutable component
unsafe {
entity
.into_mut_assume_mutable::<C>()
.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
}
},
reflect_unchecked_mut: |entity| {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
panic!("Cannot call `ReflectComponent::reflect_unchecked_mut` on component {name}. It is immutable, and cannot modified through reflection");
}
// SAFETY: reflect_unchecked_mut is an unsafe function pointer used by
// `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the component `C` on the `entity`
let c = unsafe { entity.get_mut::<C>() };
// guard ensures `C` is a mutable component
let c = unsafe { entity.get_mut_assume_mutable::<C>() };
c.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
},
register_component: |world: &mut World| -> ComponentId {

View file

@ -10,7 +10,7 @@ use crate::{
self as bevy_ecs,
bundle::{Bundle, InsertMode},
change_detection::Mut,
component::{Component, ComponentId, ComponentInfo},
component::{Component, ComponentId, ComponentInfo, Mutable},
entity::{Entities, Entity, EntityCloneBuilder},
event::{Event, SendEvent},
observer::{Observer, TriggerEvent, TriggerTargets},
@ -1787,7 +1787,7 @@ pub struct EntityEntryCommands<'a, T> {
marker: PhantomData<T>,
}
impl<'a, T: Component> EntityEntryCommands<'a, T> {
impl<'a, T: Component<Mutability = Mutable>> EntityEntryCommands<'a, T> {
/// Modify the component `T` if it exists, using the function `modify`.
pub fn and_modify(&mut self, modify: impl FnOnce(Mut<T>) + Send + Sync + 'static) -> &mut Self {
self.entity_commands
@ -1798,7 +1798,9 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
});
self
}
}
impl<'a, T: Component> EntityEntryCommands<'a, T> {
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
///
/// See also [`or_insert_with`](Self::or_insert_with).

View file

@ -3,7 +3,7 @@ use core::ops::Deref;
use crate::{
archetype::Archetype,
change_detection::MutUntyped,
component::ComponentId,
component::{ComponentId, Mutable},
entity::Entity,
event::{Event, EventId, Events, SendBatchIds},
observer::{Observers, TriggerTargets},
@ -71,7 +71,10 @@ impl<'w> DeferredWorld<'w> {
/// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given type.
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
pub fn get_mut<T: Component<Mutability = Mutable>>(
&mut self,
entity: Entity,
) -> Option<Mut<T>> {
// SAFETY:
// - `as_unsafe_world_cell` is the only thing that is borrowing world
// - `as_unsafe_world_cell` provides mutable permission to everything
@ -401,7 +404,12 @@ impl<'w> DeferredWorld<'w> {
component_id: ComponentId,
) -> Option<MutUntyped<'_>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_entity(entity)?.get_mut_by_id(component_id) }
unsafe {
self.world
.get_entity(entity)?
.get_mut_by_id(component_id)
.ok()
}
}
/// Triggers all `on_add` hooks for [`ComponentId`] in target.

View file

@ -2,7 +2,7 @@ use crate::{
archetype::{Archetype, ArchetypeId, Archetypes},
bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode},
change_detection::MutUntyped,
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType},
entity::{Entities, Entity, EntityLocation},
event::Event,
observer::{Observer, Observers},
@ -516,16 +516,28 @@ impl<'w> EntityMut<'w> {
/// Gets mutable access to the component of type `T` for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
pub fn get_mut<T: Component<Mutability = Mutable>>(&mut self) -> Option<Mut<'_, T>> {
// SAFETY: &mut self implies exclusive access for duration of returned value
unsafe { self.0.get_mut() }
}
/// Gets mutable access to the component of type `T` for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
///
/// # Safety
///
/// - `T` must be a mutable component
#[inline]
pub unsafe fn get_mut_assume_mutable<T: Component>(&mut self) -> Option<Mut<'_, T>> {
// SAFETY: &mut self implies exclusive access for duration of returned value
unsafe { self.0.get_mut_assume_mutable() }
}
/// Consumes self and gets mutable access to the component of type `T`
/// with the world `'w` lifetime for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn into_mut<T: Component>(self) -> Option<Mut<'w, T>> {
pub fn into_mut<T: Component<Mutability = Mutable>>(self) -> Option<Mut<'w, T>> {
// SAFETY: consuming `self` implies exclusive access
unsafe { self.0.get_mut() }
}
@ -1017,16 +1029,30 @@ impl<'w> EntityWorldMut<'w> {
/// Gets mutable access to the component of type `T` for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
// SAFETY: &mut self implies exclusive access for duration of returned value
unsafe { self.as_unsafe_entity_cell().get_mut() }
pub fn get_mut<T: Component<Mutability = Mutable>>(&mut self) -> Option<Mut<'_, T>> {
// SAFETY: trait bound `Mutability = Mutable` ensures `T` is mutable
unsafe { self.get_mut_assume_mutable() }
}
/// Gets mutable access to the component of type `T` for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
///
/// # Safety
///
/// - `T` must be a mutable component
#[inline]
pub unsafe fn get_mut_assume_mutable<T: Component>(&mut self) -> Option<Mut<'_, T>> {
// SAFETY:
// - &mut self implies exclusive access for duration of returned value
// - caller ensures T is mutable
unsafe { self.as_unsafe_entity_cell().get_mut_assume_mutable() }
}
/// Consumes `self` and gets mutable access to the component of type `T`
/// with the world `'w` lifetime for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn into_mut<T: Component>(self) -> Option<Mut<'w, T>> {
pub fn into_mut<T: Component<Mutability = Mutable>>(self) -> Option<Mut<'w, T>> {
// SAFETY: consuming `self` implies exclusive access
unsafe { self.into_unsafe_entity_cell().get_mut() }
}
@ -1928,7 +1954,7 @@ pub enum Entry<'w, 'a, T: Component> {
Vacant(VacantEntry<'w, 'a, T>),
}
impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
impl<'w, 'a, T: Component<Mutability = Mutable>> Entry<'w, 'a, T> {
/// Provides in-place mutable access to an occupied entry.
///
/// # Examples
@ -1954,7 +1980,9 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
Entry::Vacant(entry) => Entry::Vacant(entry),
}
}
}
impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
/// Replaces the component of the entry, and returns an [`OccupiedEntry`].
///
/// # Examples
@ -1980,7 +2008,7 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
entry.insert(component);
entry
}
Entry::Vacant(entry) => entry.insert_entry(component),
Entry::Vacant(entry) => entry.insert(component),
}
}
@ -2002,13 +2030,13 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
/// 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;
/// entity.entry().or_insert(Comp(15)).into_mut().0 *= 2;
/// assert_eq!(world.query::<&Comp>().single(&world).0, 8);
/// ```
#[inline]
pub fn or_insert(self, default: T) -> Mut<'a, T> {
pub fn or_insert(self, default: T) -> OccupiedEntry<'w, 'a, T> {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Occupied(entry) => entry,
Entry::Vacant(entry) => entry.insert(default),
}
}
@ -2030,9 +2058,9 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
/// assert_eq!(world.query::<&Comp>().single(&world).0, 4);
/// ```
#[inline]
pub fn or_insert_with<F: FnOnce() -> T>(self, default: F) -> Mut<'a, T> {
pub fn or_insert_with<F: FnOnce() -> T>(self, default: F) -> OccupiedEntry<'w, 'a, T> {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Occupied(entry) => entry,
Entry::Vacant(entry) => entry.insert(default()),
}
}
@ -2056,9 +2084,9 @@ impl<'w, 'a, T: Component + Default> Entry<'w, 'a, T> {
/// assert_eq!(world.query::<&Comp>().single(&world).0, 0);
/// ```
#[inline]
pub fn or_default(self) -> Mut<'a, T> {
pub fn or_default(self) -> OccupiedEntry<'w, 'a, T> {
match self {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Occupied(entry) => entry,
Entry::Vacant(entry) => entry.insert(Default::default()),
}
}
@ -2095,6 +2123,55 @@ impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
self.entity_world.get::<T>().unwrap()
}
/// 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 {
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
self.entity_world.take().unwrap()
}
}
impl<'w, 'a, T: Component<Mutability = Mutable>> OccupiedEntry<'w, 'a, T> {
/// Gets a mutable reference to the component in the entry.
///
/// If you need a reference to the `OccupiedEntry` which may outlive the destruction of
@ -2156,53 +2233,6 @@ impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
self.entity_world.get_mut().unwrap()
}
/// 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 {
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
self.entity_world.take().unwrap()
}
}
/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum.
@ -2212,7 +2242,7 @@ pub struct VacantEntry<'w, 'a, T: Component> {
}
impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
/// Inserts the component into the `VacantEntry` and returns a mutable reference to it.
/// Inserts the component into the `VacantEntry` and returns an `OccupiedEntry`.
///
/// # Examples
///
@ -2231,32 +2261,7 @@ impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
/// ```
#[inline]
pub fn insert(self, component: T) -> Mut<'a, T> {
self.entity_world.insert(component);
// This shouldn't panic because we just added this component
self.entity_world.get_mut::<T>().unwrap()
}
/// 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> {
pub fn insert(self, component: T) -> OccupiedEntry<'w, 'a, T> {
self.entity_world.insert(component);
OccupiedEntry {
entity_world: self.entity_world,
@ -2679,7 +2684,7 @@ impl<'w> FilteredEntityMut<'w> {
/// Gets mutable access to the component of type `T` for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
pub fn get_mut<T: Component<Mutability = Mutable>>(&mut self) -> Option<Mut<'_, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_component_write(id)
@ -2692,12 +2697,29 @@ impl<'w> FilteredEntityMut<'w> {
/// with the world `'w` lifetime for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
#[inline]
pub fn into_mut<T: Component>(self) -> Option<Mut<'w, T>> {
pub fn into_mut<T: Component<Mutability = Mutable>>(self) -> Option<Mut<'w, T>> {
// SAFETY:
// - We have write access
// - The bound `T: Component<Mutability = Mutable>` ensures the component is mutable
unsafe { self.into_mut_assume_mutable() }
}
/// Consumes self and gets mutable access to the component of type `T`
/// with the world `'w` lifetime for the current entity.
/// Returns `None` if the entity does not have a component of type `T`.
///
/// # Safety
///
/// - `T` must be a mutable component
#[inline]
pub unsafe fn into_mut_assume_mutable<T: Component>(self) -> Option<Mut<'w, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_component_write(id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut() })
// SAFETY:
// - We have write access
// - Caller ensures `T` is a mutable component
.then(|| unsafe { self.entity.get_mut_assume_mutable() })
.flatten()
}
@ -2745,7 +2767,7 @@ impl<'w> FilteredEntityMut<'w> {
self.access
.has_component_write(component_id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut_by_id(component_id) })
.then(|| unsafe { self.entity.get_mut_by_id(component_id).ok() })
.flatten()
}
}
@ -2986,7 +3008,7 @@ where
#[inline]
pub fn get_mut<C>(&mut self) -> Option<Mut<'_, C>>
where
C: Component,
C: Component<Mutability = Mutable>,
{
let components = self.entity.world().components();
let id = components.component_id::<C>()?;
@ -3308,7 +3330,8 @@ unsafe impl DynamicComponentFetch for ComponentId {
cell: UnsafeEntityCell<'_>,
) -> Result<Self::Mut<'_>, EntityComponentError> {
// SAFETY: caller ensures that the cell has mutable access to the component.
unsafe { cell.get_mut_by_id(self) }.ok_or(EntityComponentError::MissingComponent(self))
unsafe { cell.get_mut_by_id(self) }
.map_err(|_| EntityComponentError::MissingComponent(self))
}
}
@ -3377,7 +3400,7 @@ unsafe impl<const N: usize> DynamicComponentFetch for &'_ [ComponentId; N] {
*ptr = MaybeUninit::new(
// SAFETY: caller ensures that the cell has mutable access to the component.
unsafe { cell.get_mut_by_id(id) }
.ok_or(EntityComponentError::MissingComponent(id))?,
.map_err(|_| EntityComponentError::MissingComponent(id))?,
);
}
@ -3427,7 +3450,7 @@ unsafe impl DynamicComponentFetch for &'_ [ComponentId] {
ptrs.push(
// SAFETY: caller ensures that the cell has mutable access to the component.
unsafe { cell.get_mut_by_id(id) }
.ok_or(EntityComponentError::MissingComponent(id))?,
.map_err(|_| EntityComponentError::MissingComponent(id))?,
);
}
Ok(ptrs)
@ -3466,7 +3489,7 @@ unsafe impl DynamicComponentFetch for &'_ HashSet<ComponentId> {
id,
// SAFETY: caller ensures that the cell has mutable access to the component.
unsafe { cell.get_mut_by_id(id) }
.ok_or(EntityComponentError::MissingComponent(id))?,
.map_err(|_| EntityComponentError::MissingComponent(id))?,
);
}
Ok(ptrs)

View file

@ -35,8 +35,8 @@ use crate::{
change_detection::{MutUntyped, TicksMut},
component::{
Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId,
ComponentInfo, ComponentTicks, Components, RequiredComponents, RequiredComponentsError,
Tick,
ComponentInfo, ComponentTicks, Components, Mutable, RequiredComponents,
RequiredComponentsError, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation},
event::{Event, EventId, Events, SendBatchIds},
@ -1471,7 +1471,10 @@ impl World {
/// position.x = 1.0;
/// ```
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
pub fn get_mut<T: Component<Mutability = Mutable>>(
&mut self,
entity: Entity,
) -> Option<Mut<T>> {
// SAFETY:
// - `as_unsafe_world_cell` is the only thing that is borrowing world
// - `as_unsafe_world_cell` provides mutable permission to everything
@ -3718,6 +3721,7 @@ impl World {
self.as_unsafe_world_cell()
.get_entity(entity)?
.get_mut_by_id(component_id)
.ok()
}
}
}
@ -4191,6 +4195,7 @@ mod tests {
assert_eq!(data, [0, 1, 2, 3, 4, 5, 6, 7]);
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}),
true,
)
};

View file

@ -7,7 +7,7 @@ use crate::{
archetype::{Archetype, Archetypes},
bundle::Bundles,
change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells},
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::Component,
@ -21,6 +21,7 @@ use bevy_ptr::Ptr;
#[cfg(feature = "track_change_detection")]
use bevy_ptr::UnsafeCellDeref;
use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr};
use derive_more::derive::{Display, Error};
/// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid
/// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule.
@ -843,17 +844,36 @@ impl<'w> UnsafeEntityCell<'w> {
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
/// - no other references to the component exist at the same time
#[inline]
pub unsafe fn get_mut<T: Component>(self) -> Option<Mut<'w, T>> {
// SAFETY: same safety requirements
unsafe { self.get_mut_using_ticks(self.world.last_change_tick(), self.world.change_tick()) }
pub unsafe fn get_mut<T: Component<Mutability = Mutable>>(self) -> Option<Mut<'w, T>> {
// SAFETY:
// - trait bound `T: Component<Mutability = Mutable>` ensures component is mutable
// - same safety requirements
unsafe { self.get_mut_assume_mutable() }
}
/// # Safety
/// It is the callers responsibility to ensure that
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
/// - no other references to the component exist at the same time
/// - the component `T` is mutable
#[inline]
pub(crate) unsafe fn get_mut_using_ticks<T: Component>(
pub unsafe fn get_mut_assume_mutable<T: Component>(self) -> Option<Mut<'w, T>> {
// SAFETY: same safety requirements
unsafe {
self.get_mut_using_ticks_assume_mutable(
self.world.last_change_tick(),
self.world.change_tick(),
)
}
}
/// # Safety
/// It is the callers responsibility to ensure that
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
/// - no other references to the component exist at the same time
/// - The component `T` is mutable
#[inline]
pub(crate) unsafe fn get_mut_using_ticks_assume_mutable<T: Component>(
&self,
last_change_tick: Tick,
change_tick: Tick,
@ -972,8 +992,21 @@ impl<'w> UnsafeEntityCell<'w> {
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
/// - no other references to the component exist at the same time
#[inline]
pub unsafe fn get_mut_by_id(self, component_id: ComponentId) -> Option<MutUntyped<'w>> {
let info = self.world.components().get_info(component_id)?;
pub unsafe fn get_mut_by_id(
self,
component_id: ComponentId,
) -> Result<MutUntyped<'w>, GetEntityMutByIdError> {
let info = self
.world
.components()
.get_info(component_id)
.ok_or(GetEntityMutByIdError::InfoNotFound)?;
// If a component is immutable then a mutable reference to it doesn't exist
if !info.mutable() {
return Err(GetEntityMutByIdError::ComponentIsImmutable);
}
// SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe {
get_component_and_ticks(
@ -994,10 +1027,23 @@ impl<'w> UnsafeEntityCell<'w> {
#[cfg(feature = "track_change_detection")]
changed_by: _caller.deref_mut(),
})
.ok_or(GetEntityMutByIdError::ComponentNotFound)
}
}
}
/// Error that may be returned when calling [`UnsafeEntityCell::get_mut_by_id`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, Display)]
pub enum GetEntityMutByIdError {
/// The [`ComponentInfo`](crate::component::ComponentInfo) could not be found.
InfoNotFound,
/// The [`Component`] is immutable. Creating a mutable reference violates its
/// invariants.
ComponentIsImmutable,
/// This [`Entity`] does not have the desired [`Component`].
ComponentNotFound,
}
impl<'w> UnsafeWorldCell<'w> {
#[inline]
/// # Safety

View file

@ -4,7 +4,7 @@ use bevy_ecs::reflect::{
ReflectVisitEntitiesMut,
};
use bevy_ecs::{
component::{Component, ComponentCloneHandler, StorageType},
component::{Component, ComponentCloneHandler, Mutable, StorageType},
entity::{Entity, VisitEntitiesMut},
prelude::FromWorld,
world::World,
@ -42,6 +42,7 @@ 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

View file

@ -4,7 +4,7 @@ use bevy_ecs::reflect::{
ReflectVisitEntitiesMut,
};
use bevy_ecs::{
component::{Component, ComponentCloneHandler, StorageType},
component::{Component, ComponentCloneHandler, Mutable, StorageType},
entity::{Entity, VisitEntities, VisitEntitiesMut},
traversal::Traversal,
world::{FromWorld, World},
@ -42,6 +42,7 @@ pub struct Parent(pub(crate) Entity);
impl Component for Parent {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
fn get_component_clone_handler() -> ComponentCloneHandler {
ComponentCloneHandler::Ignore

View file

@ -19,7 +19,7 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChanges,
component::{Component, ComponentId},
component::{Component, ComponentId, Mutable},
entity::Entity,
event::EventReader,
prelude::{require, With},
@ -875,7 +875,7 @@ impl NormalizedRenderTarget {
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
#[allow(clippy::too_many_arguments)]
pub fn camera_system<T: CameraProjection + Component>(
pub fn camera_system<T: CameraProjection + Component<Mutability = Mutable>>(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,

View file

@ -2,7 +2,7 @@ use core::marker::PhantomData;
use crate::{primitives::Frustum, view::VisibilitySystems};
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
use bevy_ecs::prelude::*;
use bevy_ecs::{component::Mutable, prelude::*};
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
use bevy_reflect::{
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
@ -17,7 +17,9 @@ use serde::{Deserialize, Serialize};
pub struct CameraProjectionPlugin<T: CameraProjection + Component + GetTypeRegistration>(
PhantomData<T>,
);
impl<T: CameraProjection + Component + GetTypeRegistration> Plugin for CameraProjectionPlugin<T> {
impl<T: CameraProjection + Component<Mutability = Mutable> + GetTypeRegistration> Plugin
for CameraProjectionPlugin<T>
{
fn build(&self, app: &mut App) {
app.register_type::<T>()
.add_systems(

View file

@ -1,5 +1,6 @@
use bevy_color::Color;
use bevy_ecs::{
component::Mutable,
prelude::*,
system::{Query, SystemParam},
};
@ -8,7 +9,7 @@ use bevy_hierarchy::Children;
use crate::{TextColor, TextFont, TextSpan};
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
pub trait TextSpanAccess: Component {
pub trait TextSpanAccess: Component<Mutability = Mutable> {
/// Gets the text span's string.
fn read_span(&self) -> &str;
/// Gets mutable reference to the text span's string.

View file

@ -302,6 +302,7 @@ Example | Description
[Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
[Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
[Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities
[Immutable Components](../examples/ecs/immutable_components.rs) | Demonstrates the creation and utility of immutable components
[Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results
[Nondeterministic System Order](../examples/ecs/nondeterministic_system_order.rs) | Systems run in parallel, but their order isn't always deterministic. Here's how to detect and fix this.
[Observer Propagation](../examples/ecs/observer_propagation.rs) | Demonstrates event propagation with observers

View file

@ -14,7 +14,7 @@
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
use bevy::{
ecs::component::{ComponentHooks, StorageType},
ecs::component::{ComponentHooks, Mutable, StorageType},
prelude::*,
};
use std::collections::HashMap;
@ -30,6 +30,7 @@ struct MyComponent(KeyCode);
impl Component for MyComponent {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
/// Hooks can also be registered during component initialization by
/// implementing `register_component_hooks`

View file

@ -91,6 +91,7 @@ fn main() {
StorageType::Table,
Layout::array::<u64>(size).unwrap(),
None,
true,
)
});
let Some(info) = world.components().get_info(id) else {

View file

@ -0,0 +1,198 @@
//! This example demonstrates immutable components.
use bevy::{
ecs::{
component::{ComponentDescriptor, ComponentId, StorageType},
world::DeferredWorld,
},
prelude::*,
ptr::OwningPtr,
utils::HashMap,
};
use core::alloc::Layout;
/// This component is mutable, the default case. This is indicated by components
/// implementing [`Component`] where [`Component::Mutability`] is [`Mutable`](bevy::ecs::component::Mutable).
#[derive(Component)]
pub struct MyMutableComponent(bool);
/// This component is immutable. Once inserted into the ECS, it can only be viewed,
/// or removed. Replacement is also permitted, as this is equivalent to removal
/// and insertion.
///
/// Adding the `#[component(immutable)]` attribute prevents the implementation of [`Component<Mutability = Mutable>`]
/// in the derive macro.
#[derive(Component)]
#[component(immutable)]
pub struct MyImmutableComponent(bool);
fn demo_1(world: &mut World) {
// Immutable components can be inserted just like mutable components.
let mut entity = world.spawn((MyMutableComponent(false), MyImmutableComponent(false)));
// But where mutable components can be mutated...
let mut my_mutable_component = entity.get_mut::<MyMutableComponent>().unwrap();
my_mutable_component.0 = true;
// ...immutable ones cannot. The below fails to compile as `MyImmutableComponent`
// is declared as immutable.
// let mut my_immutable_component = entity.get_mut::<MyImmutableComponent>().unwrap();
// Instead, you could take or replace the immutable component to update its value.
let mut my_immutable_component = entity.take::<MyImmutableComponent>().unwrap();
my_immutable_component.0 = true;
entity.insert(my_immutable_component);
}
/// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component, Reflect)]
#[reflect(Hash, Component)]
#[component(
immutable,
// Since this component is immutable, we can fully capture all mutations through
// these component hooks. This allows for keeping other parts of the ECS synced
// to a component's value at all times.
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()
}
}
/// When a [`Name`] is inserted, we will add it to our [`NameIndex`].
///
/// Since all mutations to [`Name`] are captured by hooks, we know it is not currently
/// inserted in the index, and its value will not change without triggering a hook.
fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) {
let Some(&name) = world.entity(entity).get::<Name>() else {
unreachable!("OnInsert hook guarantees `Name` is available on entity")
};
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
return;
};
index.name_to_entity.insert(name, entity);
}
/// When a [`Name`] is removed or replaced, remove it from our [`NameIndex`].
///
/// Since all mutations to [`Name`] are captured by hooks, we know it is currently
/// inserted in the index.
fn on_replace_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) {
let Some(&name) = world.entity(entity).get::<Name>() else {
unreachable!("OnReplace hook guarantees `Name` is available on entity")
};
let Some(mut index) = world.get_resource_mut::<NameIndex>() else {
return;
};
index.name_to_entity.remove(&name);
}
fn demo_2(world: &mut World) {
// 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));
}
/// This example demonstrates how to work with _dynamic_ immutable components.
#[allow(unsafe_code)]
fn demo_3(world: &mut World) {
// This is a list of dynamic components we will create.
// The first item is the name of the component, and the second is the size
// in bytes.
let my_dynamic_components = [("Foo", 1), ("Bar", 2), ("Baz", 4)];
// This pipeline takes our component descriptions, registers them, and gets
// their ComponentId's.
let my_registered_components = my_dynamic_components
.into_iter()
.map(|(name, size)| {
// SAFETY:
// - No drop command is required
// - The component will store [u8; size], which is Send + Sync
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
name.to_string(),
StorageType::Table,
Layout::array::<u8>(size).unwrap(),
None,
false,
)
};
(name, size, descriptor)
})
.map(|(name, size, descriptor)| {
let component_id = world.register_component_with_descriptor(descriptor);
(name, size, component_id)
})
.collect::<Vec<(&str, usize, ComponentId)>>();
// Now that our components are registered, let's add them to an entity
let mut entity = world.spawn_empty();
for (_name, size, component_id) in &my_registered_components {
// We're just storing some zeroes for the sake of demonstration.
let data = core::iter::repeat_n(0, *size).collect::<Vec<u8>>();
OwningPtr::make(data, |ptr| {
// SAFETY:
// - ComponentId has been taken from the same world
// - Array is created to the layout specified in the world
unsafe {
entity.insert_by_id(*component_id, ptr);
}
});
}
for (_name, _size, component_id) in &my_registered_components {
// With immutable components, we can read the values...
assert!(entity.get_by_id(*component_id).is_ok());
// ...but we cannot gain a mutable reference.
assert!(entity.get_mut_by_id(*component_id).is_err());
// Instead, you must either remove or replace the value.
}
}
fn main() {
App::new()
.add_systems(Startup, demo_1)
.add_systems(Startup, demo_2)
.add_systems(Startup, demo_3)
.run();
}

View file

@ -2,6 +2,7 @@
use bevy::{
color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
ecs::component::Mutable,
prelude::*,
winit::WinitSettings,
};
@ -42,7 +43,7 @@ impl<T> Target<T> {
}
trait TargetUpdate {
type TargetComponent: Component;
type TargetComponent: Component<Mutability = Mutable>;
const NAME: &'static str;
fn update_target(&self, target: &mut Self::TargetComponent) -> String;
}