implement insert and remove reflected entity commands (#8895)
# Objective To enable non exclusive system usage of reflected components and make reflection more ergonomic to use by making it more in line with standard entity commands. ## Solution - Implements a new `EntityCommands` extension trait for reflection related functions in the reflect module of bevy_ecs. - Implements 4 new commands, `insert_reflect`, `insert_reflect_with_registry`, `remove_reflect`, and `remove_reflect_with_registry`. Both insert commands take a `Box<dyn Reflect>` component while the remove commands take the component type name. - Made `EntityCommands` fields pub(crate) to allow access in the reflect module. (Might be worth making these just public to enable user end custom entity commands in a different pr) - Added basic tests to ensure the commands are actually working. - Documentation of functions. --- ## Changelog Added: - Implements 4 new commands on the new entity commands extension. - `insert_reflect` - `remove_reflect` - `insert_reflect_with_registry` - `remove_reflect_with_registry` The commands operate the same except the with_registry commands take a generic parameter for a resource that implements `AsRef<TypeRegistry>`. Otherwise the default commands use the `AppTypeRegistry` for reflection data. Changed: - Made `EntityCommands` fields pub(crate) to allow access in the reflect module. > Hopefully this time it works. Please don't make me rebase again ☹
This commit is contained in:
parent
2e3900f6a9
commit
72fc63e594
452
crates/bevy_ecs/src/reflect/entity_commands.rs
Normal file
452
crates/bevy_ecs/src/reflect/entity_commands.rs
Normal file
@ -0,0 +1,452 @@
|
||||
use crate::prelude::Mut;
|
||||
use crate::reflect::AppTypeRegistry;
|
||||
use crate::system::{Command, EntityCommands, Resource};
|
||||
use crate::{entity::Entity, reflect::ReflectComponent, world::World};
|
||||
use bevy_reflect::{Reflect, TypeRegistry};
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// An extension trait for [`EntityCommands`] for reflection related functions
|
||||
pub trait ReflectCommandExt {
|
||||
/// Adds the given boxed reflect component to the entity using the reflection data in
|
||||
/// [`AppTypeRegistry`].
|
||||
///
|
||||
/// This will overwrite any previous component of the same type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the entity doesn't exist.
|
||||
/// - If [`AppTypeRegistry`] does not have the reflection data for the given [`Component`](crate::component::Component).
|
||||
/// - If the component data is invalid. See [`Reflect::apply`] for further details.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Prefer to use the typed [`EntityCommands::insert`] if possible. Adding a reflected component
|
||||
/// is much slower.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// // Note that you need to register the component type in the AppTypeRegistry prior to using
|
||||
/// // reflection. You can use the helpers on the App with `app.register_type::<ComponentA>()`
|
||||
/// // or write to the TypeRegistry directly to register all your components
|
||||
///
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::reflect::ReflectCommandExt;
|
||||
/// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};
|
||||
/// // A resource that can hold any component that implements reflect as a boxed reflect component
|
||||
/// #[derive(Resource)]
|
||||
/// struct Prefab{
|
||||
/// component: Box<dyn Reflect>,
|
||||
/// }
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
/// struct ComponentA(u32);
|
||||
///
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
/// struct ComponentB(String);
|
||||
///
|
||||
/// fn insert_reflect_component(
|
||||
/// mut commands: Commands,
|
||||
/// mut prefab: ResMut<Prefab>
|
||||
/// ) {
|
||||
/// // Create a set of new boxed reflect components to use
|
||||
/// let boxed_reflect_component_a: Box<dyn Reflect> = Box::new(ComponentA(916));
|
||||
/// let boxed_reflect_component_b: Box<dyn Reflect> = Box::new(ComponentB("NineSixteen".to_string()));
|
||||
///
|
||||
/// // You can overwrite the component in the resource with either ComponentA or ComponentB
|
||||
/// prefab.component = boxed_reflect_component_a;
|
||||
/// prefab.component = boxed_reflect_component_b;
|
||||
///
|
||||
/// // No matter which component is in the resource and without knowing the exact type, you can
|
||||
/// // use the insert_reflect entity command to insert that component into an entity.
|
||||
/// commands
|
||||
/// .spawn_empty()
|
||||
/// .insert_reflect(prefab.component.clone_value());
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
fn insert_reflect(&mut self, component: Box<dyn Reflect>) -> &mut Self;
|
||||
|
||||
/// Same as [`insert_reflect`](ReflectCommandExt::insert_reflect), but using the `T` resource as type registry instead of
|
||||
/// `AppTypeRegistry`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the given [`Resource`] is not present in the [`World`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// - The given [`Resource`] is removed from the [`World`] before the command is applied.
|
||||
fn insert_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component: Box<dyn Reflect>,
|
||||
) -> &mut Self;
|
||||
|
||||
/// Removes from the entity the component with the given type name registered in [`AppTypeRegistry`].
|
||||
///
|
||||
/// Does nothing if the entity does not have a component of the same type, if [`AppTypeRegistry`]
|
||||
/// does not contain the reflection data for the given component, or if the entity does not exist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Prefer to use the typed [`EntityCommands::remove`] if possible. Removing a reflected component
|
||||
/// is much slower.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// // Note that you need to register the component type in the AppTypeRegistry prior to using
|
||||
/// // reflection. You can use the helpers on the App with `app.register_type::<ComponentA>()`
|
||||
/// // or write to the TypeRegistry directly to register all your components
|
||||
///
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::reflect::ReflectCommandExt;
|
||||
/// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};
|
||||
///
|
||||
/// // A resource that can hold any component that implements reflect as a boxed reflect component
|
||||
/// #[derive(Resource)]
|
||||
/// struct Prefab{
|
||||
/// entity: Entity,
|
||||
/// component: Box<dyn Reflect>,
|
||||
/// }
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
/// struct ComponentA(u32);
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
/// struct ComponentB(String);
|
||||
///
|
||||
/// fn remove_reflect_component(
|
||||
/// mut commands: Commands,
|
||||
/// prefab: Res<Prefab>
|
||||
/// ) {
|
||||
/// // Prefab can hold any boxed reflect component. In this case either
|
||||
/// // ComponentA or ComponentB. No matter which component is in the resource though,
|
||||
/// // we can attempt to remove any component of that same type from an entity.
|
||||
/// commands.entity(prefab.entity)
|
||||
/// .remove_reflect(prefab.component.type_name().to_owned());
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
fn remove_reflect(&mut self, component_type_name: impl Into<Cow<'static, str>>) -> &mut Self;
|
||||
/// Same as [`remove_reflect`](ReflectCommandExt::remove_reflect), but using the `T` resource as type registry instead of
|
||||
/// `AppTypeRegistry`.
|
||||
fn remove_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component_type_name: impl Into<Cow<'static, str>>,
|
||||
) -> &mut Self;
|
||||
}
|
||||
|
||||
impl<'w, 's, 'a> ReflectCommandExt for EntityCommands<'w, 's, 'a> {
|
||||
fn insert_reflect(&mut self, component: Box<dyn Reflect>) -> &mut Self {
|
||||
self.commands.add(InsertReflect {
|
||||
entity: self.entity,
|
||||
component,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn insert_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component: Box<dyn Reflect>,
|
||||
) -> &mut Self {
|
||||
self.commands.add(InsertReflectWithRegistry::<T> {
|
||||
entity: self.entity,
|
||||
_t: PhantomData,
|
||||
component,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_reflect(&mut self, component_type_name: impl Into<Cow<'static, str>>) -> &mut Self {
|
||||
self.commands.add(RemoveReflect {
|
||||
entity: self.entity,
|
||||
component_type_name: component_type_name.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_reflect_with_registry<T: Resource + AsRef<TypeRegistry>>(
|
||||
&mut self,
|
||||
component_type_name: impl Into<Cow<'static, str>>,
|
||||
) -> &mut Self {
|
||||
self.commands.add(RemoveReflectWithRegistry::<T> {
|
||||
entity: self.entity,
|
||||
_t: PhantomData,
|
||||
component_type_name: component_type_name.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to add a reflect component to a given entity
|
||||
fn insert_reflect(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
type_registry: &TypeRegistry,
|
||||
component: Box<dyn Reflect>,
|
||||
) {
|
||||
let type_info = component.type_name();
|
||||
let Some(mut entity) = world.get_entity_mut(entity) else {
|
||||
panic!("error[B0003]: Could not insert a reflected component (of type {}) for entity {entity:?} because it doesn't exist in this World.", component.type_name());
|
||||
};
|
||||
let Some(type_registration) = type_registry.get_with_name(type_info) else {
|
||||
panic!("Could not get type registration (for component type {}) because it doesn't exist in the TypeRegistry.", component.type_name());
|
||||
};
|
||||
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
|
||||
panic!("Could not get ReflectComponent data (for component type {}) because it doesn't exist in this TypeRegistration.", component.type_name());
|
||||
};
|
||||
reflect_component.insert(&mut entity, &*component);
|
||||
}
|
||||
|
||||
/// A [`Command`] that adds the boxed reflect component to an entity using the data in
|
||||
/// [`AppTypeRegistry`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::insert_reflect`] for details.
|
||||
pub struct InsertReflect {
|
||||
/// The entity on which the component will be inserted.
|
||||
pub entity: Entity,
|
||||
/// The reflect [Component](crate::component::Component) that will be added to the entity.
|
||||
pub component: Box<dyn Reflect>,
|
||||
}
|
||||
|
||||
impl Command for InsertReflect {
|
||||
fn apply(self, world: &mut World) {
|
||||
let registry = world.get_resource::<AppTypeRegistry>().unwrap().clone();
|
||||
insert_reflect(world, self.entity, ®istry.read(), self.component);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that adds the boxed reflect component to an entity using the data in the provided
|
||||
/// [`Resource`] that implements [`AsRef<TypeRegistry>`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::insert_reflect_with_registry`] for details.
|
||||
pub struct InsertReflectWithRegistry<T: Resource + AsRef<TypeRegistry>> {
|
||||
/// The entity on which the component will be inserted.
|
||||
pub entity: Entity,
|
||||
pub _t: PhantomData<T>,
|
||||
/// The reflect [Component](crate::component::Component) that will be added to the entity.
|
||||
pub component: Box<dyn Reflect>,
|
||||
}
|
||||
|
||||
impl<T: Resource + AsRef<TypeRegistry>> Command for InsertReflectWithRegistry<T> {
|
||||
fn apply(self, world: &mut World) {
|
||||
world.resource_scope(|world, registry: Mut<T>| {
|
||||
let registry: &TypeRegistry = registry.as_ref().as_ref();
|
||||
insert_reflect(world, self.entity, registry, self.component);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to remove a reflect component from a given entity
|
||||
fn remove_reflect(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
type_registry: &TypeRegistry,
|
||||
component_type_name: Cow<'static, str>,
|
||||
) {
|
||||
let Some(mut entity) = world.get_entity_mut(entity) else {
|
||||
return;
|
||||
};
|
||||
let Some(type_registration) = type_registry.get_with_name(&component_type_name) else {
|
||||
return;
|
||||
};
|
||||
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
|
||||
return;
|
||||
};
|
||||
reflect_component.remove(&mut entity);
|
||||
}
|
||||
|
||||
/// A [`Command`] that removes the component of the same type as the given component type name from
|
||||
/// the provided entity.
|
||||
///
|
||||
/// See [`ReflectCommandExt::remove_reflect`] for details.
|
||||
pub struct RemoveReflect {
|
||||
/// The entity from which the component will be removed.
|
||||
pub entity: Entity,
|
||||
/// The [Component](crate::component::Component) type name that will be used to remove a component
|
||||
/// of the same type from the entity.
|
||||
pub component_type_name: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Command for RemoveReflect {
|
||||
fn apply(self, world: &mut World) {
|
||||
let registry = world.get_resource::<AppTypeRegistry>().unwrap().clone();
|
||||
remove_reflect(
|
||||
world,
|
||||
self.entity,
|
||||
®istry.read(),
|
||||
self.component_type_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that removes the component of the same type as the given component type name from
|
||||
/// the provided entity using the provided [`Resource`] that implements [`AsRef<TypeRegistry>`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::remove_reflect_with_registry`] for details.
|
||||
pub struct RemoveReflectWithRegistry<T: Resource + AsRef<TypeRegistry>> {
|
||||
/// The entity from which the component will be removed.
|
||||
pub entity: Entity,
|
||||
pub _t: PhantomData<T>,
|
||||
/// The [Component](crate::component::Component) type name that will be used to remove a component
|
||||
/// of the same type from the entity.
|
||||
pub component_type_name: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl<T: Resource + AsRef<TypeRegistry>> Command for RemoveReflectWithRegistry<T> {
|
||||
fn apply(self, world: &mut World) {
|
||||
world.resource_scope(|world, registry: Mut<T>| {
|
||||
let registry: &TypeRegistry = registry.as_ref().as_ref();
|
||||
remove_reflect(world, self.entity, registry, self.component_type_name);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::{AppTypeRegistry, ReflectComponent};
|
||||
use crate::reflect::ReflectCommandExt;
|
||||
use crate::system::{Commands, SystemState};
|
||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||
use bevy_ecs_macros::Resource;
|
||||
use bevy_reflect::{Reflect, TypeRegistry};
|
||||
|
||||
#[derive(Resource)]
|
||||
struct TypeRegistryResource {
|
||||
type_registry: TypeRegistry,
|
||||
}
|
||||
|
||||
impl AsRef<TypeRegistry> for TypeRegistryResource {
|
||||
fn as_ref(&self) -> &TypeRegistry {
|
||||
&self.type_registry
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, Default, PartialEq, Eq, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct ComponentA(u32);
|
||||
|
||||
#[test]
|
||||
fn insert_reflected() {
|
||||
let mut world = World::new();
|
||||
|
||||
let type_registry = AppTypeRegistry::default();
|
||||
{
|
||||
let mut registry = type_registry.write();
|
||||
registry.register::<ComponentA>();
|
||||
registry.register_type_data::<ComponentA, ReflectComponent>();
|
||||
}
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn_empty().id();
|
||||
|
||||
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert_reflect(boxed_reflect_component_a);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(
|
||||
world.entity(entity).get::<ComponentA>(),
|
||||
Some(&ComponentA(916))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_reflected_with_registry() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut type_registry = TypeRegistryResource {
|
||||
type_registry: TypeRegistry::new(),
|
||||
};
|
||||
|
||||
type_registry.type_registry.register::<ComponentA>();
|
||||
type_registry
|
||||
.type_registry
|
||||
.register_type_data::<ComponentA, ReflectComponent>();
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn_empty().id();
|
||||
|
||||
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert_reflect_with_registry::<TypeRegistryResource>(boxed_reflect_component_a);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(
|
||||
world.entity(entity).get::<ComponentA>(),
|
||||
Some(&ComponentA(916))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_reflected() {
|
||||
let mut world = World::new();
|
||||
|
||||
let type_registry = AppTypeRegistry::default();
|
||||
{
|
||||
let mut registry = type_registry.write();
|
||||
registry.register::<ComponentA>();
|
||||
registry.register_type_data::<ComponentA, ReflectComponent>();
|
||||
}
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn(ComponentA(0)).id();
|
||||
|
||||
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove_reflect(boxed_reflect_component_a.type_name().to_owned());
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.entity(entity).get::<ComponentA>(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_reflected_with_registry() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut type_registry = TypeRegistryResource {
|
||||
type_registry: TypeRegistry::new(),
|
||||
};
|
||||
|
||||
type_registry.type_registry.register::<ComponentA>();
|
||||
type_registry
|
||||
.type_registry
|
||||
.register_type_data::<ComponentA, ReflectComponent>();
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn(ComponentA(0)).id();
|
||||
|
||||
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove_reflect_with_registry::<TypeRegistryResource>(
|
||||
boxed_reflect_component_a.type_name().to_owned(),
|
||||
);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.entity(entity).get::<ComponentA>(), None);
|
||||
}
|
||||
}
|
||||
@ -8,11 +8,13 @@ use bevy_reflect::{impl_reflect_value, ReflectDeserialize, ReflectSerialize, Typ
|
||||
|
||||
mod bundle;
|
||||
mod component;
|
||||
mod entity_commands;
|
||||
mod map_entities;
|
||||
mod resource;
|
||||
|
||||
pub use bundle::{ReflectBundle, ReflectBundleFns};
|
||||
pub use component::{ReflectComponent, ReflectComponentFns};
|
||||
pub use entity_commands::ReflectCommandExt;
|
||||
pub use map_entities::ReflectMapEntities;
|
||||
pub use resource::{ReflectResource, ReflectResourceFns};
|
||||
|
||||
|
||||
@ -642,8 +642,8 @@ impl<C: EntityCommand> Command for WithEntity<C> {
|
||||
|
||||
/// A list of commands that will be run to modify an [entity](crate::entity).
|
||||
pub struct EntityCommands<'w, 's, 'a> {
|
||||
entity: Entity,
|
||||
commands: &'a mut Commands<'w, 's>,
|
||||
pub(crate) entity: Entity,
|
||||
pub(crate) commands: &'a mut Commands<'w, 's>,
|
||||
}
|
||||
|
||||
impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user