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:
Noah 2023-08-28 14:21:20 -04:00 committed by GitHub
parent 2e3900f6a9
commit 72fc63e594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 456 additions and 2 deletions

View 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, &registry.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,
&registry.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);
}
}

View File

@ -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};

View File

@ -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> {