mirror of
https://github.com/bevyengine/bevy
synced 2024-12-18 09:03:07 +00:00
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:
parent
b7bcd313ca
commit
a35811d088
27 changed files with 598 additions and 145 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -1954,6 +1954,17 @@ description = "Creates a hierarchy of parents and children entities"
|
||||||
category = "ECS (Entity Component System)"
|
category = "ECS (Entity Component System)"
|
||||||
wasm = false
|
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]]
|
[[example]]
|
||||||
name = "iter_combinations"
|
name = "iter_combinations"
|
||||||
path = "examples/ecs/iter_combinations.rs"
|
path = "examples/ecs/iter_combinations.rs"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::{Component, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
||||||
query::QueryFilter,
|
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,
|
group: &mut BenchGroup,
|
||||||
entity_count: u32,
|
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,
|
group: &mut BenchGroup,
|
||||||
entity_count: u32,
|
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,
|
group: &mut BenchGroup,
|
||||||
entity_count: u32,
|
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,
|
world: &mut World,
|
||||||
archetype_count: u16,
|
archetype_count: u16,
|
||||||
entity_count: u32,
|
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,
|
group: &mut BenchGroup,
|
||||||
archetype_count: u16,
|
archetype_count: u16,
|
||||||
entity_count: u32,
|
entity_count: u32,
|
||||||
|
|
|
@ -89,7 +89,7 @@ use core::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_ecs::component::Component;
|
use bevy_ecs::component::{Component, Mutable};
|
||||||
use bevy_math::curve::{
|
use bevy_math::curve::{
|
||||||
cores::{UnevenCore, UnevenCoreError},
|
cores::{UnevenCore, UnevenCoreError},
|
||||||
iterable::IterableCurve,
|
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>
|
impl<C, A, F> AnimatableProperty for AnimatedField<C, A, F>
|
||||||
where
|
where
|
||||||
C: Component,
|
C: Component<Mutability = Mutable>,
|
||||||
A: Animatable + Clone + Sync + Debug,
|
A: Animatable + Clone + Sync + Debug,
|
||||||
F: Fn(&mut C) -> &mut A + Send + Sync + 'static,
|
F: Fn(&mut C) -> &mut A + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
|
|
@ -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.
|
// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
|
||||||
impl Component for OrderIndependentTransparencySettings {
|
impl Component for OrderIndependentTransparencySettings {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||||
hooks.on_add(|world, entity, _| {
|
hooks.on_add(|world, entity, _| {
|
||||||
|
|
|
@ -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 {
|
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;
|
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 struct_name = &ast.ident;
|
||||||
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
|
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
|
// 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
|
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
|
||||||
TokenStream::from(quote! {
|
TokenStream::from(quote! {
|
||||||
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
|
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
|
||||||
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
|
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
|
||||||
|
type Mutability = #mutable_type;
|
||||||
fn register_required_components(
|
fn register_required_components(
|
||||||
requiree: #bevy_ecs_path::component::ComponentId,
|
requiree: #bevy_ecs_path::component::ComponentId,
|
||||||
components: &mut #bevy_ecs_path::component::Components,
|
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_REPLACE: &str = "on_replace";
|
||||||
pub const ON_REMOVE: &str = "on_remove";
|
pub const ON_REMOVE: &str = "on_remove";
|
||||||
|
|
||||||
|
pub const IMMUTABLE: &str = "immutable";
|
||||||
|
|
||||||
struct Attrs {
|
struct Attrs {
|
||||||
storage: StorageTy,
|
storage: StorageTy,
|
||||||
requires: Option<Punctuated<Require, Comma>>,
|
requires: Option<Punctuated<Require, Comma>>,
|
||||||
|
@ -198,6 +207,7 @@ struct Attrs {
|
||||||
on_insert: Option<ExprPath>,
|
on_insert: Option<ExprPath>,
|
||||||
on_replace: Option<ExprPath>,
|
on_replace: Option<ExprPath>,
|
||||||
on_remove: Option<ExprPath>,
|
on_remove: Option<ExprPath>,
|
||||||
|
immutable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -228,6 +238,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
||||||
on_replace: None,
|
on_replace: None,
|
||||||
on_remove: None,
|
on_remove: None,
|
||||||
requires: None,
|
requires: None,
|
||||||
|
immutable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut require_paths = HashSet::new();
|
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) {
|
} else if nested.path.is_ident(ON_REMOVE) {
|
||||||
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
|
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else if nested.path.is_ident(IMMUTABLE) {
|
||||||
|
attrs.immutable = true;
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(nested.error("Unsupported attribute"))
|
Err(nested.error("Unsupported attribute"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,12 +75,18 @@ pub use bevy_ecs_macros::require;
|
||||||
///
|
///
|
||||||
/// # Component and data access
|
/// # 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 [`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.
|
/// See the documentation for [`Query`] to learn how to access component data from a system.
|
||||||
///
|
///
|
||||||
/// [`entity`]: crate::entity#usage
|
/// [`entity`]: crate::entity#usage
|
||||||
/// [`Query`]: crate::system::Query
|
/// [`Query`]: crate::system::Query
|
||||||
|
/// [`ComponentMutability`]: crate::component::ComponentMutability
|
||||||
///
|
///
|
||||||
/// # Choosing a storage type
|
/// # Choosing a storage type
|
||||||
///
|
///
|
||||||
|
@ -380,6 +386,14 @@ pub trait Component: Send + Sync + 'static {
|
||||||
/// A constant indicating the storage type used for this component.
|
/// A constant indicating the storage type used for this component.
|
||||||
const STORAGE_TYPE: StorageType;
|
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`].
|
/// Called when registering this component, allowing mutable access to its [`ComponentHooks`].
|
||||||
fn register_component_hooks(_hooks: &mut 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.
|
/// The storage used for a specific component type.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -626,6 +695,12 @@ impl ComponentInfo {
|
||||||
&self.descriptor.name
|
&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 the [`TypeId`] of the underlying component type.
|
||||||
/// Returns `None` if the component does not correspond to a Rust type.
|
/// Returns `None` if the component does not correspond to a Rust type.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -778,6 +853,7 @@ pub struct ComponentDescriptor {
|
||||||
// this descriptor describes.
|
// this descriptor describes.
|
||||||
// None if the underlying type doesn't need to be dropped
|
// None if the underlying type doesn't need to be dropped
|
||||||
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
||||||
|
mutable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to ignore the `drop` field in our `Debug` impl
|
// 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("is_send_and_sync", &self.is_send_and_sync)
|
||||||
.field("type_id", &self.type_id)
|
.field("type_id", &self.type_id)
|
||||||
.field("layout", &self.layout)
|
.field("layout", &self.layout)
|
||||||
|
.field("mutable", &self.mutable)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -813,6 +890,7 @@ impl ComponentDescriptor {
|
||||||
type_id: Some(TypeId::of::<T>()),
|
type_id: Some(TypeId::of::<T>()),
|
||||||
layout: Layout::new::<T>(),
|
layout: Layout::new::<T>(),
|
||||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
||||||
|
mutable: T::Mutability::MUTABLE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,6 +904,7 @@ impl ComponentDescriptor {
|
||||||
storage_type: StorageType,
|
storage_type: StorageType,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
||||||
|
mutable: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
@ -834,6 +913,7 @@ impl ComponentDescriptor {
|
||||||
type_id: None,
|
type_id: None,
|
||||||
layout,
|
layout,
|
||||||
drop,
|
drop,
|
||||||
|
mutable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,6 +930,7 @@ impl ComponentDescriptor {
|
||||||
type_id: Some(TypeId::of::<T>()),
|
type_id: Some(TypeId::of::<T>()),
|
||||||
layout: Layout::new::<T>(),
|
layout: Layout::new::<T>(),
|
||||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
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>()),
|
type_id: Some(TypeId::of::<T>()),
|
||||||
layout: Layout::new::<T>(),
|
layout: Layout::new::<T>(),
|
||||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
||||||
|
mutable: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,6 +964,12 @@ impl ComponentDescriptor {
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
self.name.as_ref()
|
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.
|
/// Function type that can be used to clone an entity.
|
||||||
|
|
|
@ -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):
|
/// 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::prelude::*;
|
||||||
/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler};
|
/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler, Mutable};
|
||||||
/// #[derive(Clone)]
|
/// #[derive(Clone)]
|
||||||
/// struct SomeComponent;
|
/// struct SomeComponent;
|
||||||
///
|
///
|
||||||
/// impl Component for SomeComponent {
|
/// impl Component for SomeComponent {
|
||||||
/// const STORAGE_TYPE: StorageType = StorageType::Table;
|
/// const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||||
|
/// type Mutability = Mutable;
|
||||||
/// fn get_component_clone_handler() -> ComponentCloneHandler {
|
/// fn get_component_clone_handler() -> ComponentCloneHandler {
|
||||||
/// ComponentCloneHandler::Custom(component_clone_via_clone::<Self>)
|
/// ComponentCloneHandler::Custom(component_clone_via_clone::<Self>)
|
||||||
/// }
|
/// }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, ComponentCloneHandler, ComponentHooks, StorageType},
|
component::{Component, ComponentCloneHandler, ComponentHooks, Mutable, StorageType},
|
||||||
entity::{Entity, EntityCloneBuilder, EntityCloner},
|
entity::{Entity, EntityCloneBuilder, EntityCloner},
|
||||||
observer::ObserverState,
|
observer::ObserverState,
|
||||||
world::{DeferredWorld, World},
|
world::{DeferredWorld, World},
|
||||||
|
@ -11,6 +11,7 @@ pub(crate) struct ObservedBy(pub(crate) Vec<Entity>);
|
||||||
|
|
||||||
impl Component for ObservedBy {
|
impl Component for ObservedBy {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||||
hooks.on_remove(|mut world, entity, _| {
|
hooks.on_remove(|mut world, entity, _| {
|
||||||
|
|
|
@ -456,7 +456,7 @@ impl World {
|
||||||
// Populate ObservedBy for each observed entity.
|
// Populate ObservedBy for each observed entity.
|
||||||
for watched_entity in &(*observer_state).descriptor.entities {
|
for watched_entity in &(*observer_state).descriptor.entities {
|
||||||
let mut entity_mut = self.entity_mut(*watched_entity);
|
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);
|
observed_by.0.push(observer_entity);
|
||||||
}
|
}
|
||||||
(&*observer_state, &mut self.archetypes, &mut self.observers)
|
(&*observer_state, &mut self.archetypes, &mut self.observers)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentHook, ComponentHooks, ComponentId, StorageType},
|
component::{ComponentHook, ComponentHooks, ComponentId, Mutable, StorageType},
|
||||||
observer::{ObserverDescriptor, ObserverTrigger},
|
observer::{ObserverDescriptor, ObserverTrigger},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
|
@ -62,6 +62,7 @@ impl ObserverState {
|
||||||
|
|
||||||
impl Component for ObserverState {
|
impl Component for ObserverState {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||||
hooks.on_add(|mut world, entity, _| {
|
hooks.on_add(|mut world, entity, _| {
|
||||||
|
@ -314,6 +315,7 @@ impl Observer {
|
||||||
|
|
||||||
impl Component for Observer {
|
impl Component for Observer {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||||
|
type Mutability = Mutable;
|
||||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||||
hooks.on_add(|world, entity, _id| {
|
hooks.on_add(|world, entity, _id| {
|
||||||
let Some(observe) = world.get::<Self>(entity) else {
|
let Some(observe) = world.get::<Self>(entity) else {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
archetype::{Archetype, Archetypes},
|
archetype::{Archetype, Archetypes},
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
|
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
|
||||||
component::{Component, ComponentId, Components, StorageType, Tick},
|
component::{Component, ComponentId, Components, Mutable, StorageType, Tick},
|
||||||
entity::{Entities, Entity, EntityLocation},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||||
storage::{ComponentSparseSet, Table, TableRow},
|
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`
|
/// 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;
|
type ReadOnly = &'__w T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,14 +60,16 @@
|
||||||
use super::from_reflect_with_fallback;
|
use super::from_reflect_with_fallback;
|
||||||
use crate::{
|
use crate::{
|
||||||
change_detection::Mut,
|
change_detection::Mut,
|
||||||
component::{Component, ComponentId},
|
component::{ComponentId, ComponentMutability},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
prelude::Component,
|
||||||
world::{
|
world::{
|
||||||
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
|
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
|
||||||
FilteredEntityRef, World,
|
FilteredEntityRef, World,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry};
|
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry};
|
||||||
|
use disqualified::ShortName;
|
||||||
|
|
||||||
/// A struct used to operate on reflected [`Component`] trait of a type.
|
/// A struct used to operate on reflected [`Component`] trait of a type.
|
||||||
///
|
///
|
||||||
|
@ -150,11 +152,17 @@ impl ReflectComponent {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if there is no [`Component`] of the given type.
|
/// 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) {
|
pub fn apply<'a>(&self, entity: impl Into<EntityMut<'a>>, component: &dyn PartialReflect) {
|
||||||
(self.0.apply)(entity.into(), component);
|
(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.
|
/// 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(
|
pub fn apply_or_insert(
|
||||||
&self,
|
&self,
|
||||||
entity: &mut EntityWorldMut,
|
entity: &mut EntityWorldMut,
|
||||||
|
@ -180,6 +188,10 @@ impl ReflectComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value of this [`Component`] type from the entity as a mutable reflected reference.
|
/// 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>(
|
pub fn reflect_mut<'a>(
|
||||||
&self,
|
&self,
|
||||||
entity: impl Into<FilteredEntityMut<'a>>,
|
entity: impl Into<FilteredEntityMut<'a>>,
|
||||||
|
@ -192,6 +204,10 @@ impl ReflectComponent {
|
||||||
/// violating Rust's aliasing rules. To avoid this:
|
/// 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`
|
/// * 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`].
|
/// * 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>(
|
pub unsafe fn reflect_unchecked_mut<'a>(
|
||||||
&self,
|
&self,
|
||||||
entity: UnsafeEntityCell<'a>,
|
entity: UnsafeEntityCell<'a>,
|
||||||
|
@ -265,6 +281,8 @@ impl ReflectComponent {
|
||||||
|
|
||||||
impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
|
impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
|
||||||
fn from_type() -> Self {
|
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 {
|
ReflectComponent(ReflectComponentFns {
|
||||||
insert: |entity, reflected_component, registry| {
|
insert: |entity, reflected_component, registry| {
|
||||||
let component = entity.world_scope(|world| {
|
let component = entity.world_scope(|world| {
|
||||||
|
@ -273,11 +291,23 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
|
||||||
entity.insert(component);
|
entity.insert(component);
|
||||||
},
|
},
|
||||||
apply: |mut entity, reflected_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);
|
component.apply(reflected_component);
|
||||||
},
|
},
|
||||||
apply_or_insert: |entity, reflected_component, registry| {
|
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());
|
component.apply(reflected_component.as_partial_reflect());
|
||||||
} else {
|
} else {
|
||||||
let component = entity.world_scope(|world| {
|
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: |entity| entity.get::<C>().map(|c| c as &dyn Reflect),
|
||||||
reflect_mut: |entity| {
|
reflect_mut: |entity| {
|
||||||
entity
|
if !C::Mutability::MUTABLE {
|
||||||
.into_mut::<C>()
|
let name = ShortName::of::<C>();
|
||||||
.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
|
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| {
|
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
|
// 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`
|
// `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))
|
c.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
|
||||||
},
|
},
|
||||||
register_component: |world: &mut World| -> ComponentId {
|
register_component: |world: &mut World| -> ComponentId {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
bundle::{Bundle, InsertMode},
|
bundle::{Bundle, InsertMode},
|
||||||
change_detection::Mut,
|
change_detection::Mut,
|
||||||
component::{Component, ComponentId, ComponentInfo},
|
component::{Component, ComponentId, ComponentInfo, Mutable},
|
||||||
entity::{Entities, Entity, EntityCloneBuilder},
|
entity::{Entities, Entity, EntityCloneBuilder},
|
||||||
event::{Event, SendEvent},
|
event::{Event, SendEvent},
|
||||||
observer::{Observer, TriggerEvent, TriggerTargets},
|
observer::{Observer, TriggerEvent, TriggerTargets},
|
||||||
|
@ -1787,7 +1787,7 @@ pub struct EntityEntryCommands<'a, T> {
|
||||||
marker: PhantomData<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`.
|
/// 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 {
|
pub fn and_modify(&mut self, modify: impl FnOnce(Mut<T>) + Send + Sync + 'static) -> &mut Self {
|
||||||
self.entity_commands
|
self.entity_commands
|
||||||
|
@ -1798,7 +1798,9 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Component> EntityEntryCommands<'a, T> {
|
||||||
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
|
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
|
||||||
///
|
///
|
||||||
/// See also [`or_insert_with`](Self::or_insert_with).
|
/// See also [`or_insert_with`](Self::or_insert_with).
|
||||||
|
|
|
@ -3,7 +3,7 @@ use core::ops::Deref;
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::Archetype,
|
archetype::Archetype,
|
||||||
change_detection::MutUntyped,
|
change_detection::MutUntyped,
|
||||||
component::ComponentId,
|
component::{ComponentId, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventId, Events, SendBatchIds},
|
event::{Event, EventId, Events, SendBatchIds},
|
||||||
observer::{Observers, TriggerTargets},
|
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.
|
/// 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.
|
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
|
||||||
#[inline]
|
#[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:
|
// SAFETY:
|
||||||
// - `as_unsafe_world_cell` is the only thing that is borrowing world
|
// - `as_unsafe_world_cell` is the only thing that is borrowing world
|
||||||
// - `as_unsafe_world_cell` provides mutable permission to everything
|
// - `as_unsafe_world_cell` provides mutable permission to everything
|
||||||
|
@ -401,7 +404,12 @@ impl<'w> DeferredWorld<'w> {
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> Option<MutUntyped<'_>> {
|
) -> Option<MutUntyped<'_>> {
|
||||||
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
|
// 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.
|
/// Triggers all `on_add` hooks for [`ComponentId`] in target.
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
archetype::{Archetype, ArchetypeId, Archetypes},
|
archetype::{Archetype, ArchetypeId, Archetypes},
|
||||||
bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode},
|
bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode},
|
||||||
change_detection::MutUntyped,
|
change_detection::MutUntyped,
|
||||||
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
|
component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType},
|
||||||
entity::{Entities, Entity, EntityLocation},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
event::Event,
|
event::Event,
|
||||||
observer::{Observer, Observers},
|
observer::{Observer, Observers},
|
||||||
|
@ -516,16 +516,28 @@ impl<'w> EntityMut<'w> {
|
||||||
/// Gets mutable access to the component of type `T` for the current entity.
|
/// 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`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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
|
// SAFETY: &mut self implies exclusive access for duration of returned value
|
||||||
unsafe { self.0.get_mut() }
|
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`
|
/// Consumes self and gets mutable access to the component of type `T`
|
||||||
/// with the world `'w` lifetime for the current entity.
|
/// with the world `'w` lifetime for the current entity.
|
||||||
/// Returns `None` if the entity does not have a component of type `T`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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
|
// SAFETY: consuming `self` implies exclusive access
|
||||||
unsafe { self.0.get_mut() }
|
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.
|
/// 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`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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
|
// SAFETY: trait bound `Mutability = Mutable` ensures `T` is mutable
|
||||||
unsafe { self.as_unsafe_entity_cell().get_mut() }
|
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`
|
/// Consumes `self` and gets mutable access to the component of type `T`
|
||||||
/// with the world `'w` lifetime for the current entity.
|
/// with the world `'w` lifetime for the current entity.
|
||||||
/// Returns `None` if the entity does not have a component of type `T`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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
|
// SAFETY: consuming `self` implies exclusive access
|
||||||
unsafe { self.into_unsafe_entity_cell().get_mut() }
|
unsafe { self.into_unsafe_entity_cell().get_mut() }
|
||||||
}
|
}
|
||||||
|
@ -1928,7 +1954,7 @@ pub enum Entry<'w, 'a, T: Component> {
|
||||||
Vacant(VacantEntry<'w, 'a, T>),
|
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.
|
/// Provides in-place mutable access to an occupied entry.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -1954,7 +1980,9 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||||
Entry::Vacant(entry) => Entry::Vacant(entry),
|
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`].
|
/// Replaces the component of the entry, and returns an [`OccupiedEntry`].
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
@ -1980,7 +2008,7 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||||
entry.insert(component);
|
entry.insert(component);
|
||||||
entry
|
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);
|
/// assert_eq!(world.query::<&Comp>().single(&world).0, 4);
|
||||||
///
|
///
|
||||||
/// # let mut entity = world.get_entity_mut(entity_id).unwrap();
|
/// # 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);
|
/// assert_eq!(world.query::<&Comp>().single(&world).0, 8);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn or_insert(self, default: T) -> Mut<'a, T> {
|
pub fn or_insert(self, default: T) -> OccupiedEntry<'w, 'a, T> {
|
||||||
match self {
|
match self {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry,
|
||||||
Entry::Vacant(entry) => entry.insert(default),
|
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);
|
/// assert_eq!(world.query::<&Comp>().single(&world).0, 4);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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 {
|
match self {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry,
|
||||||
Entry::Vacant(entry) => entry.insert(default()),
|
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);
|
/// assert_eq!(world.query::<&Comp>().single(&world).0, 0);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn or_default(self) -> Mut<'a, T> {
|
pub fn or_default(self) -> OccupiedEntry<'w, 'a, T> {
|
||||||
match self {
|
match self {
|
||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry,
|
||||||
Entry::Vacant(entry) => entry.insert(Default::default()),
|
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()
|
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.
|
/// Gets a mutable reference to the component in the entry.
|
||||||
///
|
///
|
||||||
/// If you need a reference to the `OccupiedEntry` which may outlive the destruction of
|
/// 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.
|
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
|
||||||
self.entity_world.get_mut().unwrap()
|
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.
|
/// 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> {
|
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
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -2231,32 +2261,7 @@ impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
|
||||||
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
|
/// assert_eq!(world.query::<&Comp>().single(&world).0, 10);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert(self, component: T) -> Mut<'a, T> {
|
pub fn insert(self, component: T) -> OccupiedEntry<'w, '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> {
|
|
||||||
self.entity_world.insert(component);
|
self.entity_world.insert(component);
|
||||||
OccupiedEntry {
|
OccupiedEntry {
|
||||||
entity_world: self.entity_world,
|
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.
|
/// 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`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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>())?;
|
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
|
||||||
self.access
|
self.access
|
||||||
.has_component_write(id)
|
.has_component_write(id)
|
||||||
|
@ -2692,12 +2697,29 @@ impl<'w> FilteredEntityMut<'w> {
|
||||||
/// with the world `'w` lifetime for the current entity.
|
/// with the world `'w` lifetime for the current entity.
|
||||||
/// Returns `None` if the entity does not have a component of type `T`.
|
/// Returns `None` if the entity does not have a component of type `T`.
|
||||||
#[inline]
|
#[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>())?;
|
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
|
||||||
self.access
|
self.access
|
||||||
.has_component_write(id)
|
.has_component_write(id)
|
||||||
// SAFETY: We have write access
|
// SAFETY:
|
||||||
.then(|| unsafe { self.entity.get_mut() })
|
// - We have write access
|
||||||
|
// - Caller ensures `T` is a mutable component
|
||||||
|
.then(|| unsafe { self.entity.get_mut_assume_mutable() })
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2745,7 +2767,7 @@ impl<'w> FilteredEntityMut<'w> {
|
||||||
self.access
|
self.access
|
||||||
.has_component_write(component_id)
|
.has_component_write(component_id)
|
||||||
// SAFETY: We have write access
|
// 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()
|
.flatten()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2986,7 +3008,7 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_mut<C>(&mut self) -> Option<Mut<'_, C>>
|
pub fn get_mut<C>(&mut self) -> Option<Mut<'_, C>>
|
||||||
where
|
where
|
||||||
C: Component,
|
C: Component<Mutability = Mutable>,
|
||||||
{
|
{
|
||||||
let components = self.entity.world().components();
|
let components = self.entity.world().components();
|
||||||
let id = components.component_id::<C>()?;
|
let id = components.component_id::<C>()?;
|
||||||
|
@ -3308,7 +3330,8 @@ unsafe impl DynamicComponentFetch for ComponentId {
|
||||||
cell: UnsafeEntityCell<'_>,
|
cell: UnsafeEntityCell<'_>,
|
||||||
) -> Result<Self::Mut<'_>, EntityComponentError> {
|
) -> Result<Self::Mut<'_>, EntityComponentError> {
|
||||||
// SAFETY: caller ensures that the cell has mutable access to the component.
|
// 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(
|
*ptr = MaybeUninit::new(
|
||||||
// SAFETY: caller ensures that the cell has mutable access to the component.
|
// SAFETY: caller ensures that the cell has mutable access to the component.
|
||||||
unsafe { cell.get_mut_by_id(id) }
|
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(
|
ptrs.push(
|
||||||
// SAFETY: caller ensures that the cell has mutable access to the component.
|
// SAFETY: caller ensures that the cell has mutable access to the component.
|
||||||
unsafe { cell.get_mut_by_id(id) }
|
unsafe { cell.get_mut_by_id(id) }
|
||||||
.ok_or(EntityComponentError::MissingComponent(id))?,
|
.map_err(|_| EntityComponentError::MissingComponent(id))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(ptrs)
|
Ok(ptrs)
|
||||||
|
@ -3466,7 +3489,7 @@ unsafe impl DynamicComponentFetch for &'_ HashSet<ComponentId> {
|
||||||
id,
|
id,
|
||||||
// SAFETY: caller ensures that the cell has mutable access to the component.
|
// SAFETY: caller ensures that the cell has mutable access to the component.
|
||||||
unsafe { cell.get_mut_by_id(id) }
|
unsafe { cell.get_mut_by_id(id) }
|
||||||
.ok_or(EntityComponentError::MissingComponent(id))?,
|
.map_err(|_| EntityComponentError::MissingComponent(id))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(ptrs)
|
Ok(ptrs)
|
||||||
|
|
|
@ -35,8 +35,8 @@ use crate::{
|
||||||
change_detection::{MutUntyped, TicksMut},
|
change_detection::{MutUntyped, TicksMut},
|
||||||
component::{
|
component::{
|
||||||
Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId,
|
Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId,
|
||||||
ComponentInfo, ComponentTicks, Components, RequiredComponents, RequiredComponentsError,
|
ComponentInfo, ComponentTicks, Components, Mutable, RequiredComponents,
|
||||||
Tick,
|
RequiredComponentsError, Tick,
|
||||||
},
|
},
|
||||||
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation},
|
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation},
|
||||||
event::{Event, EventId, Events, SendBatchIds},
|
event::{Event, EventId, Events, SendBatchIds},
|
||||||
|
@ -1471,7 +1471,10 @@ impl World {
|
||||||
/// position.x = 1.0;
|
/// position.x = 1.0;
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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:
|
// SAFETY:
|
||||||
// - `as_unsafe_world_cell` is the only thing that is borrowing world
|
// - `as_unsafe_world_cell` is the only thing that is borrowing world
|
||||||
// - `as_unsafe_world_cell` provides mutable permission to everything
|
// - `as_unsafe_world_cell` provides mutable permission to everything
|
||||||
|
@ -3718,6 +3721,7 @@ impl World {
|
||||||
self.as_unsafe_world_cell()
|
self.as_unsafe_world_cell()
|
||||||
.get_entity(entity)?
|
.get_entity(entity)?
|
||||||
.get_mut_by_id(component_id)
|
.get_mut_by_id(component_id)
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4191,6 +4195,7 @@ mod tests {
|
||||||
assert_eq!(data, [0, 1, 2, 3, 4, 5, 6, 7]);
|
assert_eq!(data, [0, 1, 2, 3, 4, 5, 6, 7]);
|
||||||
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
|
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||||
}),
|
}),
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
archetype::{Archetype, Archetypes},
|
archetype::{Archetype, Archetypes},
|
||||||
bundle::Bundles,
|
bundle::Bundles,
|
||||||
change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut},
|
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},
|
entity::{Entities, Entity, EntityLocation},
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
prelude::Component,
|
prelude::Component,
|
||||||
|
@ -21,6 +21,7 @@ use bevy_ptr::Ptr;
|
||||||
#[cfg(feature = "track_change_detection")]
|
#[cfg(feature = "track_change_detection")]
|
||||||
use bevy_ptr::UnsafeCellDeref;
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr};
|
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
|
/// 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.
|
/// 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
|
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
|
||||||
/// - no other references to the component exist at the same time
|
/// - no other references to the component exist at the same time
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn get_mut<T: Component>(self) -> Option<Mut<'w, T>> {
|
pub unsafe fn get_mut<T: Component<Mutability = Mutable>>(self) -> Option<Mut<'w, T>> {
|
||||||
// SAFETY: same safety requirements
|
// SAFETY:
|
||||||
unsafe { self.get_mut_using_ticks(self.world.last_change_tick(), self.world.change_tick()) }
|
// - trait bound `T: Component<Mutability = Mutable>` ensures component is mutable
|
||||||
|
// - same safety requirements
|
||||||
|
unsafe { self.get_mut_assume_mutable() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// It is the callers responsibility to ensure that
|
/// It is the callers responsibility to ensure that
|
||||||
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
|
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
|
||||||
/// - no other references to the component exist at the same time
|
/// - no other references to the component exist at the same time
|
||||||
|
/// - the component `T` is mutable
|
||||||
#[inline]
|
#[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,
|
&self,
|
||||||
last_change_tick: Tick,
|
last_change_tick: Tick,
|
||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
|
@ -972,8 +992,21 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
|
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
|
||||||
/// - no other references to the component exist at the same time
|
/// - no other references to the component exist at the same time
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn get_mut_by_id(self, component_id: ComponentId) -> Option<MutUntyped<'w>> {
|
pub unsafe fn get_mut_by_id(
|
||||||
let info = self.world.components().get_info(component_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
|
// SAFETY: entity_location is valid, component_id is valid as checked by the line above
|
||||||
unsafe {
|
unsafe {
|
||||||
get_component_and_ticks(
|
get_component_and_ticks(
|
||||||
|
@ -994,10 +1027,23 @@ impl<'w> UnsafeEntityCell<'w> {
|
||||||
#[cfg(feature = "track_change_detection")]
|
#[cfg(feature = "track_change_detection")]
|
||||||
changed_by: _caller.deref_mut(),
|
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> {
|
impl<'w> UnsafeWorldCell<'w> {
|
||||||
#[inline]
|
#[inline]
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bevy_ecs::reflect::{
|
||||||
ReflectVisitEntitiesMut,
|
ReflectVisitEntitiesMut,
|
||||||
};
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::{Component, ComponentCloneHandler, StorageType},
|
component::{Component, ComponentCloneHandler, Mutable, StorageType},
|
||||||
entity::{Entity, VisitEntitiesMut},
|
entity::{Entity, VisitEntitiesMut},
|
||||||
prelude::FromWorld,
|
prelude::FromWorld,
|
||||||
world::World,
|
world::World,
|
||||||
|
@ -42,6 +42,7 @@ pub struct Children(pub(crate) SmallVec<[Entity; 8]>);
|
||||||
|
|
||||||
impl Component for Children {
|
impl Component for Children {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
fn get_component_clone_handler() -> ComponentCloneHandler {
|
fn get_component_clone_handler() -> ComponentCloneHandler {
|
||||||
ComponentCloneHandler::Ignore
|
ComponentCloneHandler::Ignore
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bevy_ecs::reflect::{
|
||||||
ReflectVisitEntitiesMut,
|
ReflectVisitEntitiesMut,
|
||||||
};
|
};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::{Component, ComponentCloneHandler, StorageType},
|
component::{Component, ComponentCloneHandler, Mutable, StorageType},
|
||||||
entity::{Entity, VisitEntities, VisitEntitiesMut},
|
entity::{Entity, VisitEntities, VisitEntitiesMut},
|
||||||
traversal::Traversal,
|
traversal::Traversal,
|
||||||
world::{FromWorld, World},
|
world::{FromWorld, World},
|
||||||
|
@ -42,6 +42,7 @@ pub struct Parent(pub(crate) Entity);
|
||||||
|
|
||||||
impl Component for Parent {
|
impl Component for Parent {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
fn get_component_clone_handler() -> ComponentCloneHandler {
|
fn get_component_clone_handler() -> ComponentCloneHandler {
|
||||||
ComponentCloneHandler::Ignore
|
ComponentCloneHandler::Ignore
|
||||||
|
|
|
@ -19,7 +19,7 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
component::{Component, ComponentId},
|
component::{Component, ComponentId, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
prelude::{require, With},
|
prelude::{require, With},
|
||||||
|
@ -875,7 +875,7 @@ impl NormalizedRenderTarget {
|
||||||
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
||||||
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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_resized_events: EventReader<WindowResized>,
|
||||||
mut window_created_events: EventReader<WindowCreated>,
|
mut window_created_events: EventReader<WindowCreated>,
|
||||||
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{primitives::Frustum, view::VisibilitySystems};
|
use crate::{primitives::Frustum, view::VisibilitySystems};
|
||||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
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_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{
|
||||||
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
|
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
|
||||||
|
@ -17,7 +17,9 @@ use serde::{Deserialize, Serialize};
|
||||||
pub struct CameraProjectionPlugin<T: CameraProjection + Component + GetTypeRegistration>(
|
pub struct CameraProjectionPlugin<T: CameraProjection + Component + GetTypeRegistration>(
|
||||||
PhantomData<T>,
|
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) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<T>()
|
app.register_type::<T>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use bevy_color::Color;
|
use bevy_color::Color;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
component::Mutable,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{Query, SystemParam},
|
system::{Query, SystemParam},
|
||||||
};
|
};
|
||||||
|
@ -8,7 +9,7 @@ use bevy_hierarchy::Children;
|
||||||
use crate::{TextColor, TextFont, TextSpan};
|
use crate::{TextColor, TextFont, TextSpan};
|
||||||
|
|
||||||
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
|
/// 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.
|
/// Gets the text span's string.
|
||||||
fn read_span(&self) -> &str;
|
fn read_span(&self) -> &str;
|
||||||
/// Gets mutable reference to the text span's string.
|
/// Gets mutable reference to the text span's string.
|
||||||
|
|
|
@ -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
|
[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
|
[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
|
[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
|
[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.
|
[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
|
[Observer Propagation](../examples/ecs/observer_propagation.rs) | Demonstrates event propagation with observers
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
|
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
ecs::component::{ComponentHooks, StorageType},
|
ecs::component::{ComponentHooks, Mutable, StorageType},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -30,6 +30,7 @@ struct MyComponent(KeyCode);
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||||
|
type Mutability = Mutable;
|
||||||
|
|
||||||
/// Hooks can also be registered during component initialization by
|
/// Hooks can also be registered during component initialization by
|
||||||
/// implementing `register_component_hooks`
|
/// implementing `register_component_hooks`
|
||||||
|
|
|
@ -91,6 +91,7 @@ fn main() {
|
||||||
StorageType::Table,
|
StorageType::Table,
|
||||||
Layout::array::<u64>(size).unwrap(),
|
Layout::array::<u64>(size).unwrap(),
|
||||||
None,
|
None,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let Some(info) = world.components().get_info(id) else {
|
let Some(info) = world.components().get_info(id) else {
|
||||||
|
|
198
examples/ecs/immutable_components.rs
Normal file
198
examples/ecs/immutable_components.rs
Normal 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();
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
|
color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
|
||||||
|
ecs::component::Mutable,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
winit::WinitSettings,
|
winit::WinitSettings,
|
||||||
};
|
};
|
||||||
|
@ -42,7 +43,7 @@ impl<T> Target<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait TargetUpdate {
|
trait TargetUpdate {
|
||||||
type TargetComponent: Component;
|
type TargetComponent: Component<Mutability = Mutable>;
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
fn update_target(&self, target: &mut Self::TargetComponent) -> String;
|
fn update_target(&self, target: &mut Self::TargetComponent) -> String;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue