From 203cf793c3ee0a434a091e49a7646ce54c8cfa3a Mon Sep 17 00:00:00 2001 From: Gino Valente Date: Sat, 22 Oct 2022 22:20:36 -0700 Subject: [PATCH] Add ReflectBundle --- assets/scenes/load_scene_example.scn.ron | 17 +- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/reflect.rs | 212 +++++++++++++++++- crates/bevy_scene/src/dynamic_scene.rs | 20 ++ .../bevy_scene/src/dynamic_scene_builder.rs | 1 + crates/bevy_scene/src/scene_spawner.rs | 2 + crates/bevy_scene/src/serde.rs | 78 +++++++ examples/scene/scene.rs | 26 +++ 8 files changed, 349 insertions(+), 9 deletions(-) diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index de4d3ce9280a0..8963f152da75c 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -25,12 +25,17 @@ }, ), 1: ( - components: { - "scene::ComponentA": ( - x: 3.0, - y: 4.0, - ), - }, + components: {}, + // Note: The `bundles` map is optional, but allows entire bundles to be spawned (if registered) + bundles: { + "scene::SomeBundle": ( + // You may optionally override specific components of a bundle + a: ( + x: 3.14, + y: 2.72, + ), + ) + } ), } ) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 04bc2ed7ffec4..9f1056f20f95a 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -24,7 +24,7 @@ pub use bevy_ptr as ptr; pub mod prelude { #[doc(hidden)] #[cfg(feature = "bevy_reflect")] - pub use crate::reflect::{ReflectComponent, ReflectResource}; + pub use crate::reflect::{ReflectBundle, ReflectComponent, ReflectResource}; #[doc(hidden)] pub use crate::{ bundle::Bundle, diff --git a/crates/bevy_ecs/src/reflect.rs b/crates/bevy_ecs/src/reflect.rs index 88e25dfaf86b5..34129c8ce332f 100644 --- a/crates/bevy_ecs/src/reflect.rs +++ b/crates/bevy_ecs/src/reflect.rs @@ -1,5 +1,6 @@ //! Types that enable reflection support. +use crate::bundle::Bundle; use crate::{ change_detection::Mut, component::Component, @@ -8,9 +9,10 @@ use crate::{ world::{FromWorld, World}, }; use bevy_reflect::{ - impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize, - ReflectSerialize, + impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize, ReflectRef, + ReflectSerialize, TypeRegistry, }; +use std::any::TypeId; /// A struct used to operate on reflected [`Component`] of a type. /// @@ -225,6 +227,212 @@ impl FromType for ReflectComponent { } } +/// A struct used to operate on reflected [`Bundle`] of a type. +/// +/// A [`ReflectBundle`] for type `T` can be obtained via +/// [`bevy_reflect::TypeRegistration::data`]. +#[derive(Clone)] +pub struct ReflectBundle(ReflectBundleFns); + +/// The raw function pointers needed to make up a [`ReflectBundle`]. +/// +/// This is used when creating custom implementations of [`ReflectBundle`] with +/// [`ReflectBundle::new()`]. +/// +/// > **Note:** +/// > Creating custom implementations of [`ReflectBundle`] is an advanced feature that most users +/// > will not need. +/// > Usually a [`ReflectBundle`] is created for a type by deriving [`Reflect`] +/// > and adding the `#[reflect(Bundle)]` attribute. +/// > After adding the bundle to the [`TypeRegistry`][bevy_reflect::TypeRegistry], +/// > its [`ReflectBundle`] can then be retrieved when needed. +/// +/// Creating a custom [`ReflectBundle`] may be useful if you need to create new bundle types +/// at runtime, for example, for scripting implementations. +/// +/// By creating a custom [`ReflectBundle`] and inserting it into a type's +/// [`TypeRegistration`][bevy_reflect::TypeRegistration], +/// you can modify the way that reflected bundles of that type will be inserted into the Bevy +/// world. +#[derive(Clone)] +pub struct ReflectBundleFns { + /// Function pointer implementing [`ReflectBundle::insert()`]. + pub insert: fn(&mut World, Entity, &dyn Reflect), + /// Function pointer implementing [`ReflectBundle::apply()`]. + pub apply: fn(&mut World, Entity, &dyn Reflect, &TypeRegistry), + /// Function pointer implementing [`ReflectBundle::apply_or_insert()`]. + pub apply_or_insert: fn(&mut World, Entity, &dyn Reflect, &TypeRegistry), + /// Function pointer implementing [`ReflectBundle::remove()`]. + pub remove: fn(&mut World, Entity), +} + +impl ReflectBundleFns { + /// Get the default set of [`ReflectComponentFns`] for a specific component type using its + /// [`FromType`] implementation. + /// + /// This is useful if you want to start with the default implementation before overriding some + /// of the functions to create a custom implementation. + pub fn new() -> Self { + >::from_type().0 + } +} + +impl ReflectBundle { + /// Insert a reflected [`Bundle`] into the entity like [`insert()`](crate::world::EntityMut::insert). + /// + /// # Panics + /// + /// Panics if there is no such entity. + pub fn insert(&self, world: &mut World, entity: Entity, bundle: &dyn Reflect) { + (self.0.insert)(world, entity, bundle); + } + + /// Uses reflection to set the value of this [`Bundle`] type in the entity to the given value. + /// + /// # Panics + /// + /// Panics if there is no [`Bundle`] of the given type or the `entity` does not exist. + pub fn apply( + &self, + world: &mut World, + entity: Entity, + bundle: &dyn Reflect, + registry: &TypeRegistry, + ) { + (self.0.apply)(world, entity, bundle, registry); + } + + /// Uses reflection to set the value of this [`Bundle`] type in the entity to the given value or insert a new one if it does not exist. + /// + /// # Panics + /// + /// Panics if the `entity` does not exist. + pub fn apply_or_insert( + &self, + world: &mut World, + entity: Entity, + bundle: &dyn Reflect, + registry: &TypeRegistry, + ) { + (self.0.apply_or_insert)(world, entity, bundle, registry); + } + + /// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist. + /// + /// # Panics + /// + /// Panics if there is no [`Bundle`] of the given type or the `entity` does not exist. + pub fn remove(&self, world: &mut World, entity: Entity) { + (self.0.remove)(world, entity); + } + + /// Create a custom implementation of [`ReflectBundle`]. + /// + /// This is an advanced feature, + /// useful for scripting implementations, + /// that should not be used by most users + /// unless you know what you are doing. + /// + /// Usually you should derive [`Reflect`] and add the `#[reflect(Bundle)]` component + /// to generate a [`ReflectBundle`] implementation automatically. + /// + /// See [`ReflectComponentFns`] for more information. + pub fn new(fns: ReflectBundleFns) -> Self { + Self(fns) + } +} + +impl FromType for ReflectBundle { + fn from_type() -> Self { + ReflectBundle(ReflectBundleFns { + insert: |world, entity, reflected_bundle| { + let mut bundle = C::from_world(world); + bundle.apply(reflected_bundle); + world.entity_mut(entity).insert(bundle); + }, + apply: |world, entity, reflected_bundle, registry| { + let mut bundle = C::from_world(world); + bundle.apply(reflected_bundle); + + if let ReflectRef::Struct(bundle) = bundle.reflect_ref() { + for field in bundle.iter_fields() { + if let Some(reflect_component) = + registry.get_type_data::(field.type_id()) + { + reflect_component.apply(world, entity, field); + } else if let Some(reflect_bundle) = + registry.get_type_data::(field.type_id()) + { + reflect_bundle.apply(world, entity, field, registry); + } else { + if let Some(id) = world.bundles().get_id(TypeId::of::()) { + let info = world.bundles().get(id).unwrap(); + if info.components().is_empty() { + panic!( + "no `ReflectComponent` registration found for `{}`", + field.type_name() + ); + } + }; + + panic!( + "no `ReflectBundle` registration found for `{}`", + field.type_name() + ) + } + } + } else { + panic!( + "expected bundle `{}` to be named struct", + std::any::type_name::() + ); + } + }, + apply_or_insert: |world, entity, reflected_bundle, registry| { + let mut bundle = C::from_world(world); + bundle.apply(reflected_bundle); + + if let ReflectRef::Struct(bundle) = bundle.reflect_ref() { + for field in bundle.iter_fields() { + if let Some(reflect_component) = + registry.get_type_data::(field.type_id()) + { + reflect_component.apply_or_insert(world, entity, field); + } else if let Some(reflect_bundle) = + registry.get_type_data::(field.type_id()) + { + reflect_bundle.apply_or_insert(world, entity, field, registry); + } else { + if let Some(id) = world.bundles().get_id(TypeId::of::()) { + let info = world.bundles().get(id).unwrap(); + if info.components().is_empty() { + panic!( + "no `ReflectComponent` registration found for `{}`", + field.type_name() + ); + } + }; + + panic!( + "no `ReflectBundle` registration found for `{}`", + field.type_name() + ) + } + } + } else { + panic!( + "expected bundle `{}` to be named struct", + std::any::type_name::() + ); + } + }, + remove: |world, entity| { + world.entity_mut(entity).remove::(); + }, + }) + } +} + /// A struct used to operate on reflected [`Resource`] of a type. /// /// A [`ReflectResource`] for type `T` can be obtained via diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 29bc8eab02372..a0048e17d8ea8 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,6 +1,7 @@ use crate::{serde::SceneSerializer, DynamicSceneBuilder, Scene, SceneSpawnError}; use anyhow::Result; use bevy_app::AppTypeRegistry; +use bevy_ecs::reflect::ReflectBundle; use bevy_ecs::{ entity::EntityMap, reflect::{ReflectComponent, ReflectMapEntities}, @@ -29,6 +30,9 @@ pub struct DynamicEntity { /// A vector of boxed components that belong to the given entity and /// implement the `Reflect` trait. pub components: Vec>, + /// A vector of boxed bundles that belong to the given entity and + /// implement the `Reflect` trait. + pub bundles: Vec>, } impl DynamicScene { @@ -68,6 +72,22 @@ impl DynamicScene { .entry(bevy_ecs::entity::Entity::from_raw(scene_entity.entity)) .or_insert_with(|| world.spawn_empty().id()); + for bundle in &scene_entity.bundles { + let registration = + type_registry + .get_with_name(bundle.type_name()) + .ok_or_else(|| SceneSpawnError::UnregisteredType { + type_name: bundle.type_name().to_string(), + })?; + let reflect_bundle = registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredBundle { + type_name: bundle.type_name().to_string(), + } + })?; + + reflect_bundle.apply_or_insert(world, entity, &**bundle, &type_registry); + } + // Apply/ add each component to the given entity. for component in &scene_entity.components { let registration = type_registry diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 841af6756f3ba..4600c2f24e87b 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -109,6 +109,7 @@ impl<'w> DynamicSceneBuilder<'w> { let mut entry = DynamicEntity { entity: index, components: Vec::new(), + bundles: Vec::new(), }; for component_id in self.world.entity(entity).archetype().components() { diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 1a377c6b7ae07..4e6bb1b616380 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -45,6 +45,8 @@ pub struct SceneSpawner { pub enum SceneSpawnError { #[error("scene contains the unregistered component `{type_name}`. consider adding `#[reflect(Component)]` to your type")] UnregisteredComponent { type_name: String }, + #[error("scene contains the unregistered bundle `{type_name}`. consider adding `#[reflect(Bundle)]` to your type")] + UnregisteredBundle { type_name: String }, #[error("scene contains the unregistered type `{type_name}`. consider registering the type using `app.register_type::()`")] UnregisteredType { type_name: String }, #[error("scene does not exist")] diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 0a225690168d2..d15a66d0b3b3c 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -16,6 +16,7 @@ pub const SCENE_ENTITIES: &str = "entities"; pub const ENTITY_STRUCT: &str = "Entity"; pub const ENTITY_FIELD_COMPONENTS: &str = "components"; +pub const ENTITY_FIELD_BUNDLES: &str = "bundles"; pub struct SceneSerializer<'a> { pub scene: &'a DynamicScene, @@ -122,6 +123,7 @@ enum SceneField { #[serde(field_identifier, rename_all = "lowercase")] enum EntityField { Components, + Bundles, } pub struct SceneDeserializer<'a> { @@ -283,9 +285,16 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { })? .ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?; + let bundles = seq + .next_element_seed(BundleDeserializer { + registry: self.registry, + })? + .unwrap_or_default(); + Ok(DynamicEntity { entity: self.id, components, + bundles, }) } @@ -294,6 +303,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { A: MapAccess<'de>, { let mut components = None; + let mut bundles = None; while let Some(key) = map.next_key()? { match key { EntityField::Components => { @@ -305,15 +315,28 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { registry: self.registry, })?); } + EntityField::Bundles => { + if bundles.is_some() { + return Err(Error::duplicate_field(ENTITY_FIELD_BUNDLES)); + } + + bundles = Some(map.next_value_seed(BundleDeserializer { + registry: self.registry, + })?); + } } } let components = components .take() .ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?; + + let bundles = bundles.unwrap_or_default(); + Ok(DynamicEntity { entity: self.id, components, + bundles, }) } } @@ -384,6 +407,59 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> { } } +pub struct BundleDeserializer<'a> { + pub registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for BundleDeserializer<'a> { + type Value = Vec>; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(BundleVisitor { + registry: self.registry, + }) + } +} + +struct BundleVisitor<'a> { + pub registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for BundleVisitor<'a> { + type Value = Vec>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("map of bundles") + } + + fn visit_map(self, mut map: A) -> std::result::Result + where + A: MapAccess<'de>, + { + let mut added_keys = HashSet::new(); + let mut bundles = Vec::new(); + while let Some(key) = map.next_key::<&str>()? { + if !added_keys.insert(key) { + return Err(Error::custom(format!("duplicate bundle: `{}`", key))); + } + + let registration = self + .registry + .get_with_name(key) + .ok_or_else(|| Error::custom(format!("no registration found for `{}`", key)))?; + + bundles.push( + map.next_value_seed(TypedReflectDeserializer::new(registration, self.registry))?, + ); + } + + Ok(bundles) + } +} + #[cfg(test)] mod tests { use crate::serde::{SceneDeserializer, SceneSerializer}; @@ -398,9 +474,11 @@ mod tests { #[derive(Component, Reflect, Default)] #[reflect(Component)] struct Foo(i32); + #[derive(Component, Reflect, Default)] #[reflect(Component)] struct Bar(i32); + #[derive(Component, Reflect, Default)] #[reflect(Component)] struct Baz(i32); diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index a3210214ad7b9..f4e6bc5fa0850 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -14,6 +14,7 @@ fn main() { })) .register_type::() .register_type::() + .register_type::() .add_startup_system(save_scene_system) .add_startup_system(load_scene_system) .add_startup_system(infotext_system) @@ -56,6 +57,31 @@ impl FromWorld for ComponentB { } } +/// For bundles, you can add `#[reflect(Bundle)]` to enable deserializing entire bundles. +/// +/// This can be useful when manually creating scene files. Rather than writing out each component in the bundle, +/// you can simply add the bundle to the `bundles` field of the scene. +/// You can then modify specific components of that bundle if you need to, or just leave it blank and let it +/// generate a default instance of that bundle. The choice is yours! +/// +/// Note: This only works for _deserializing_ scenes. When serializing a scene, the `bundles` field will +/// not be generated and all of the bundle's components will instead be added to the `components` field. +#[derive(Reflect, Bundle)] +#[reflect(Bundle)] +struct SomeBundle { + a: ComponentA, + b: ComponentB, +} + +impl FromWorld for SomeBundle { + fn from_world(world: &mut World) -> Self { + Self { + a: ComponentA { x: -1.0, y: -1.0 }, + b: ComponentB::from_world(world), + } + } +} + // The initial scene file will be loaded below and not change when the scene is saved const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron";