diff --git a/Cargo.toml b/Cargo.toml index e943f81f61..24813a9207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/benches/benches/bevy_ecs/change_detection.rs b/benches/benches/bevy_ecs/change_detection.rs index 6c4428efed..f0a3eaa614 100644 --- a/benches/benches/bevy_ecs/change_detection.rs +++ b/benches/benches/bevy_ecs/change_detection.rs @@ -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( +fn all_changed_detection_generic + Default + BenchModify>( group: &mut BenchGroup, entity_count: u32, ) { @@ -172,7 +172,7 @@ fn all_changed_detection(criterion: &mut Criterion) { } } -fn few_changed_detection_generic( +fn few_changed_detection_generic + Default + BenchModify>( group: &mut BenchGroup, entity_count: u32, ) { @@ -222,7 +222,7 @@ fn few_changed_detection(criterion: &mut Criterion) { } } -fn none_changed_detection_generic( +fn none_changed_detection_generic + Default>( group: &mut BenchGroup, entity_count: u32, ) { @@ -271,7 +271,7 @@ fn insert_if_bit_enabled(entity: &mut EntityWorldMut, i: u16) { } } -fn add_archetypes_entities( +fn add_archetypes_entities + Default>( world: &mut World, archetype_count: u16, entity_count: u32, @@ -298,7 +298,7 @@ fn add_archetypes_entities( } } } -fn multiple_archetype_none_changed_detection_generic( +fn multiple_archetype_none_changed_detection_generic + Default + BenchModify>( group: &mut BenchGroup, archetype_count: u16, entity_count: u32, diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index d91f376b79..e41f83157b 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -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 &mut A> { impl AnimatableProperty for AnimatedField where - C: Component, + C: Component, A: Animatable + Clone + Sync + Debug, F: Fn(&mut C) -> &mut A + Send + Sync + 'static, { diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 45fbdf2344..754b67a2a7 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -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, _| { diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 9a205b91e7..62a84b6896 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -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>, @@ -198,6 +207,7 @@ struct Attrs { on_insert: Option, on_replace: Option, on_remove: Option, + immutable: bool, } #[derive(Clone, Copy)] @@ -228,6 +238,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { 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 { } else if nested.path.is_ident(ON_REMOVE) { attrs.on_remove = Some(nested.value()?.parse::()?); Ok(()) + } else if nested.path.is_ident(IMMUTABLE) { + attrs.immutable = true; + Ok(()) } else { Err(nested.error("Unsupported attribute")) } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 5bdaeb024a..9bdb0cb6ff 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -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`], + /// while immutable components will instead have [`Component`]. + /// + /// * 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 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::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), + mutable: T::Mutability::MUTABLE, } } @@ -826,6 +904,7 @@ impl ComponentDescriptor { storage_type: StorageType, layout: Layout, drop: Option 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::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), + mutable: true, } } @@ -861,6 +942,7 @@ impl ComponentDescriptor { type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: 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. diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 710b96e5a3..da0e51c25f 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -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::) /// } diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 8433d3cea3..6fd39a4b42 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -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); 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, _| { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index cec04b94ea..53e8b1dec2 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -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::().or_default(); + let mut observed_by = entity_mut.entry::().or_default().into_mut(); observed_by.0.push(observer_entity); } (&*observer_state, &mut self.archetypes, &mut self.observers) diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index f2bfb465c8..64aebd33cd 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -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::(entity) else { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 6bd32117dd..a56754d95b 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -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> QueryData for &'__w mut T { type ReadOnly = &'__w T; } diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index a735aa7532..7778f16451 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -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>, 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>, @@ -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 FromType 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 FromType for ReflectComponent { entity.insert(component); }, apply: |mut entity, reflected_component| { - let mut component = entity.get_mut::().unwrap(); + if !C::Mutability::MUTABLE { + let name = ShortName::of::(); + 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::() }.unwrap(); component.apply(reflected_component); }, apply_or_insert: |entity, reflected_component, registry| { - if let Some(mut component) = entity.get_mut::() { + if !C::Mutability::MUTABLE { + let name = ShortName::of::(); + 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::() } { component.apply(reflected_component.as_partial_reflect()); } else { let component = entity.world_scope(|world| { @@ -300,14 +330,28 @@ impl FromType for ReflectComponent { }, reflect: |entity| entity.get::().map(|c| c as &dyn Reflect), reflect_mut: |entity| { - entity - .into_mut::() - .map(|c| c.map_unchanged(|value| value as &mut dyn Reflect)) + if !C::Mutability::MUTABLE { + let name = ShortName::of::(); + 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::() + .map(|c| c.map_unchanged(|value| value as &mut dyn Reflect)) + } }, reflect_unchecked_mut: |entity| { + if !C::Mutability::MUTABLE { + let name = ShortName::of::(); + 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::() }; + // guard ensures `C` is a mutable component + let c = unsafe { entity.get_mut_assume_mutable::() }; c.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect)) }, register_component: |world: &mut World| -> ComponentId { diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index aa5523b0bd..899bd92a4d 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -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, } -impl<'a, T: Component> EntityEntryCommands<'a, T> { +impl<'a, T: Component> EntityEntryCommands<'a, T> { /// Modify the component `T` if it exists, using the function `modify`. pub fn and_modify(&mut self, modify: impl FnOnce(Mut) + 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). diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 0ffd94e01e..fd31c3edf1 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -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(&mut self, entity: Entity) -> Option> { + pub fn get_mut>( + &mut self, + entity: Entity, + ) -> Option> { // 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> { // 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. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index b5e66974d8..ec6830150c 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -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(&mut self) -> Option> { + pub fn get_mut>(&mut self) -> Option> { // 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(&mut self) -> Option> { + // 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(self) -> Option> { + pub fn into_mut>(self) -> Option> { // 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(&mut self) -> Option> { - // SAFETY: &mut self implies exclusive access for duration of returned value - unsafe { self.as_unsafe_entity_cell().get_mut() } + pub fn get_mut>(&mut self) -> Option> { + // 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(&mut self) -> Option> { + // 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(self) -> Option> { + pub fn into_mut>(self) -> Option> { // 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> 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 T>(self, default: F) -> Mut<'a, T> { + pub fn or_insert_with 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::().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::() { + /// 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::() { + /// 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> 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::() { - /// 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::() { - /// 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::().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::() { - /// 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(&mut self) -> Option> { + pub fn get_mut>(&mut self) -> Option> { let id = self.entity.world().components().get_id(TypeId::of::())?; 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(self) -> Option> { + pub fn into_mut>(self) -> Option> { + // SAFETY: + // - We have write access + // - The bound `T: Component` 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(self) -> Option> { let id = self.entity.world().components().get_id(TypeId::of::())?; 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(&mut self) -> Option> where - C: Component, + C: Component, { let components = self.entity.world().components(); let id = components.component_id::()?; @@ -3308,7 +3330,8 @@ unsafe impl DynamicComponentFetch for ComponentId { cell: UnsafeEntityCell<'_>, ) -> Result, 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 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 { 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) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2d51122e24..491c3648dd 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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(&mut self, entity: Entity) -> Option> { + pub fn get_mut>( + &mut self, + entity: Entity, + ) -> Option> { // 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, ) }; diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 4c2813702a..1a4a3d343b 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -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(self) -> Option> { - // SAFETY: same safety requirements - unsafe { self.get_mut_using_ticks(self.world.last_change_tick(), self.world.change_tick()) } + pub unsafe fn get_mut>(self) -> Option> { + // SAFETY: + // - trait bound `T: Component` 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( + pub unsafe fn get_mut_assume_mutable(self) -> Option> { + // 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( &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> { - let info = self.world.components().get_info(component_id)?; + pub unsafe fn get_mut_by_id( + self, + component_id: ComponentId, + ) -> Result, 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 diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index b811c1da2d..a561d3cd9d 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -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 diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 445857bb18..9217cb1521 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -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 diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 8badda2a1b..df0b4fd043 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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( +pub fn camera_system>( mut window_resized_events: EventReader, mut window_created_events: EventReader, mut window_scale_factor_changed_events: EventReader, diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 7deb061fdc..4c10a706df 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -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( PhantomData, ); -impl Plugin for CameraProjectionPlugin { +impl + GetTypeRegistration> Plugin + for CameraProjectionPlugin +{ fn build(&self, app: &mut App) { app.register_type::() .add_systems( diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 2d0c7dcfe5..84943ea566 100644 --- a/crates/bevy_text/src/text_access.rs +++ b/crates/bevy_text/src/text_access.rs @@ -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 { /// Gets the text span's string. fn read_span(&self) -> &str; /// Gets mutable reference to the text span's string. diff --git a/examples/README.md b/examples/README.md index 8a0b68a4b3..2e48c40886 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index b9c903f33c..8786e1d12a 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -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` diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index 7cec3a47fd..1e9b8c57a3 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -91,6 +91,7 @@ fn main() { StorageType::Table, Layout::array::(size).unwrap(), None, + true, ) }); let Some(info) = world.components().get_info(id) else { diff --git a/examples/ecs/immutable_components.rs b/examples/ecs/immutable_components.rs new file mode 100644 index 0000000000..2e9b54522c --- /dev/null +++ b/examples/ecs/immutable_components.rs @@ -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`] +/// 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::().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::().unwrap(); + + // Instead, you could take or replace the immutable component to update its value. + let mut my_immutable_component = entity.take::().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, +} + +impl NameIndex { + fn get_entity(&self, name: &'static str) -> Option { + 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::() else { + unreachable!("OnInsert hook guarantees `Name` is available on entity") + }; + let Some(mut index) = world.get_resource_mut::() 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::() else { + unreachable!("OnReplace hook guarantees `Name` is available on entity") + }; + let Some(mut index) = world.get_resource_mut::() else { + return; + }; + + index.name_to_entity.remove(&name); +} + +fn demo_2(world: &mut World) { + // Setup our name index + world.init_resource::(); + + // Spawn some entities! + let alyssa = world.spawn(Name("Alyssa")).id(); + let javier = world.spawn(Name("Javier")).id(); + + // Check our index + let index = world.resource::(); + + 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::(); + + 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::(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::>(); + + // 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::>(); + + 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(); +} diff --git a/examples/ui/display_and_visibility.rs b/examples/ui/display_and_visibility.rs index ca3f29376c..48cf17c767 100644 --- a/examples/ui/display_and_visibility.rs +++ b/examples/ui/display_and_visibility.rs @@ -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 Target { } trait TargetUpdate { - type TargetComponent: Component; + type TargetComponent: Component; const NAME: &'static str; fn update_target(&self, target: &mut Self::TargetComponent) -> String; }