Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_scene: Add ReflectBundle #6344

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions assets/scenes/load_scene_example.scn.ron
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
)
}
),
}
)
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
212 changes: 210 additions & 2 deletions crates/bevy_ecs/src/reflect.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Types that enable reflection support.

use crate::bundle::Bundle;
use crate::{
change_detection::Mut,
component::Component,
Expand All @@ -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.
///
Expand Down Expand Up @@ -225,6 +227,212 @@ impl<C: Component + Reflect + FromWorld> FromType<C> 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<T: Bundle + Reflect + FromWorld>() -> Self {
<ReflectBundle as FromType<T>>::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<C: Bundle + Reflect + FromWorld> FromType<C> 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::<ReflectComponent>(field.type_id())
{
reflect_component.apply(world, entity, field);
} else if let Some(reflect_bundle) =
registry.get_type_data::<ReflectBundle>(field.type_id())
{
reflect_bundle.apply(world, entity, field, registry);
} else {
if let Some(id) = world.bundles().get_id(TypeId::of::<C>()) {
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::<C>()
);
}
},
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::<ReflectComponent>(field.type_id())
{
reflect_component.apply_or_insert(world, entity, field);
} else if let Some(reflect_bundle) =
registry.get_type_data::<ReflectBundle>(field.type_id())
{
reflect_bundle.apply_or_insert(world, entity, field, registry);
} else {
if let Some(id) = world.bundles().get_id(TypeId::of::<C>()) {
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::<C>()
);
}
},
remove: |world, entity| {
world.entity_mut(entity).remove::<C>();
},
})
}
}

/// A struct used to operate on reflected [`Resource`] of a type.
///
/// A [`ReflectResource`] for type `T` can be obtained via
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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<Box<dyn Reflect>>,
/// A vector of boxed bundles that belong to the given entity and
/// implement the `Reflect` trait.
pub bundles: Vec<Box<dyn Reflect>>,
}

impl DynamicScene {
Expand Down Expand Up @@ -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::<ReflectBundle>().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
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_scene/src/dynamic_scene_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>()`")]
UnregisteredType { type_name: String },
#[error("scene does not exist")]
Expand Down
Loading