Skip to content

Commit

Permalink
Make from_reflect_or_world also try ReflectDefault and improve so…
Browse files Browse the repository at this point in the history
…me comments and panic messages (#12499)

# Objective

- `from_reflect_or_world` is an internal utilty used in the
implementations of `ReflectComponent` and `ReflectBundle` to create a
`T` given a `&dyn Reflect` by trying to use `FromReflect`, and if that
fails it falls back to `ReflectFromWorld`
- reflecting `FromWorld` is not intuitive though: often it is implicitly
implemented by deriving `Default` so people might not even be aware of
it.
- the panic messages mentioning `ReflectFromWorld` are not directly
correlated to what the user would have to do (reflect `FromWorld`)

## Solution

- Also check for `ReflectDefault` in addition to `ReflectFromWorld`.
- Change the panic messages to mention the reflected trait rather than
the `Reflect*` types.

---

## Changelog

- `ReflectComponent` and `ReflectBundle` no longer require `T:
FromReflect` but instead only `T: Reflect`.
- `ReflectComponent` and `ReflectBundle` will also work with types that
only reflected `Default` and not `FromWorld`.

## Migration Guide

- `ReflectBundle::insert` now requires an additional `&TypeRegistry`
parameter.
  • Loading branch information
SkiFire13 authored Apr 30, 2024
1 parent 6c57a16 commit d9b6973
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 35 deletions.
21 changes: 14 additions & 7 deletions crates/bevy_ecs/src/reflect/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
};
use bevy_reflect::{FromReflect, FromType, Reflect, ReflectRef, TypeRegistry};

use super::ReflectComponent;
use super::{from_reflect_with_fallback, ReflectComponent};

/// A struct used to operate on reflected [`Bundle`] trait of a type.
///
Expand All @@ -27,7 +27,7 @@ pub struct ReflectBundle(ReflectBundleFns);
#[derive(Clone)]
pub struct ReflectBundleFns {
/// Function pointer implementing [`ReflectBundle::insert()`].
pub insert: fn(&mut EntityWorldMut, &dyn Reflect),
pub insert: fn(&mut EntityWorldMut, &dyn Reflect, &TypeRegistry),
/// Function pointer implementing [`ReflectBundle::apply()`].
pub apply: fn(EntityMut, &dyn Reflect, &TypeRegistry),
/// Function pointer implementing [`ReflectBundle::apply_or_insert()`].
Expand All @@ -49,8 +49,13 @@ impl ReflectBundleFns {

impl ReflectBundle {
/// Insert a reflected [`Bundle`] into the entity like [`insert()`](EntityWorldMut::insert).
pub fn insert(&self, entity: &mut EntityWorldMut, bundle: &dyn Reflect) {
(self.0.insert)(entity, bundle);
pub fn insert(
&self,
entity: &mut EntityWorldMut,
bundle: &dyn Reflect,
registry: &TypeRegistry,
) {
(self.0.insert)(entity, bundle, registry);
}

/// Uses reflection to set the value of this [`Bundle`] type in the entity to the given value.
Expand Down Expand Up @@ -117,11 +122,13 @@ impl ReflectBundle {
}
}

impl<B: Bundle + Reflect + FromReflect> FromType<B> for ReflectBundle {
impl<B: Bundle + Reflect> FromType<B> for ReflectBundle {
fn from_type() -> Self {
ReflectBundle(ReflectBundleFns {
insert: |entity, reflected_bundle| {
let bundle = B::from_reflect(reflected_bundle).unwrap();
insert: |entity, reflected_bundle, registry| {
let bundle = entity.world_scope(|world| {
from_reflect_with_fallback::<B>(reflected_bundle, world, registry)
});
entity.insert(bundle);
},
apply: |mut entity, reflected_bundle, registry| {
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_ecs/src/reflect/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
//!
//! [`get_type_registration`]: bevy_reflect::GetTypeRegistration::get_type_registration
use super::from_reflect_or_world;
use super::from_reflect_with_fallback;
use crate::{
change_detection::Mut,
component::Component,
Expand Down Expand Up @@ -253,12 +253,12 @@ impl ReflectComponent {
}
}

impl<C: Component + Reflect + FromReflect> FromType<C> for ReflectComponent {
impl<C: Component + Reflect> FromType<C> for ReflectComponent {
fn from_type() -> Self {
ReflectComponent(ReflectComponentFns {
insert: |entity, reflected_component, registry| {
let component = entity.world_scope(|world| {
from_reflect_or_world::<C>(reflected_component, world, registry)
from_reflect_with_fallback::<C>(reflected_component, world, registry)
});
entity.insert(component);
},
Expand All @@ -271,7 +271,7 @@ impl<C: Component + Reflect + FromReflect> FromType<C> for ReflectComponent {
component.apply(reflected_component);
} else {
let component = entity.world_scope(|world| {
from_reflect_or_world::<C>(reflected_component, world, registry)
from_reflect_with_fallback::<C>(reflected_component, world, registry)
});
entity.insert(component);
}
Expand All @@ -283,7 +283,7 @@ impl<C: Component + Reflect + FromReflect> FromType<C> for ReflectComponent {
copy: |source_world, destination_world, source_entity, destination_entity, registry| {
let source_component = source_world.get::<C>(source_entity).unwrap();
let destination_component =
from_reflect_or_world::<C>(source_component, destination_world, registry);
from_reflect_with_fallback::<C>(source_component, destination_world, registry);
destination_world
.entity_mut(destination_entity)
.insert(destination_component);
Expand Down
72 changes: 53 additions & 19 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::ops::{Deref, DerefMut};

use crate as bevy_ecs;
use crate::{system::Resource, world::World};
use bevy_reflect::{FromReflect, Reflect, TypeRegistry, TypeRegistryArc};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::{Reflect, ReflectFromReflect, TypeRegistry, TypeRegistryArc};

mod bundle;
mod component;
Expand Down Expand Up @@ -44,35 +45,68 @@ impl DerefMut for AppTypeRegistry {

/// Creates a `T` from a `&dyn Reflect`.
///
/// The first approach uses `T`'s implementation of `FromReflect`.
/// If this fails, it falls back to default-initializing a new instance of `T` using its
/// `ReflectFromWorld` data from the `world`'s `AppTypeRegistry` and `apply`ing the
/// `&dyn Reflect` on it.
/// This will try the following strategies, in this order:
///
/// Panics if both approaches fail.
fn from_reflect_or_world<T: FromReflect>(
/// - use the reflected `FromReflect`, if it's present and doesn't fail;
/// - use the reflected `Default`, if it's present, and then call `apply` on the result;
/// - use the reflected `FromWorld`, just like the `Default`.
///
/// The first one that is present and doesn't fail will be used.
///
/// # Panics
///
/// If any strategy produces a `Box<dyn Reflect>` that doesn't store a value of type `T`
/// this method will panic.
///
/// If none of the strategies succeed, this method will panic.
fn from_reflect_with_fallback<T: Reflect>(
reflected: &dyn Reflect,
world: &mut World,
registry: &TypeRegistry,
) -> T {
if let Some(value) = T::from_reflect(reflected) {
return value;
}

// Clone the `ReflectFromWorld` because it's cheap and "frees"
// the borrow of `world` so that it can be passed to `from_world`.
let Some(reflect_from_world) = registry.get_type_data::<ReflectFromWorld>(TypeId::of::<T>())
else {
fn different_type_error<T>(reflected: &str) -> ! {
panic!(
"`FromReflect` failed and no `ReflectFromWorld` registration found for `{}`",
"The registration for the reflected `{}` trait for the type `{}` produced \
a value of a different type",
reflected,
// FIXME: once we have unique reflect, use `TypePath`.
std::any::type_name::<T>(),
);
};
}

// First, try `FromReflect`. This is handled differently from the others because
// it doesn't need a subsequent `apply` and may fail.
if let Some(reflect_from_reflect) =
registry.get_type_data::<ReflectFromReflect>(TypeId::of::<T>())
{
// If it fails it's ok, we can continue checking `Default` and `FromWorld`.
if let Some(value) = reflect_from_reflect.from_reflect(reflected) {
return value
.take::<T>()
.unwrap_or_else(|_| different_type_error::<T>("FromReflect"));
}
}

let Ok(mut value) = reflect_from_world.from_world(world).take::<T>() else {
// Create an instance of `T` using either the reflected `Default` or `FromWorld`.
let mut value = if let Some(reflect_default) =
registry.get_type_data::<ReflectDefault>(TypeId::of::<T>())
{
reflect_default
.default()
.take::<T>()
.unwrap_or_else(|_| different_type_error::<T>("Default"))
} else if let Some(reflect_from_world) =
registry.get_type_data::<ReflectFromWorld>(TypeId::of::<T>())
{
reflect_from_world
.from_world(world)
.take::<T>()
.unwrap_or_else(|_| different_type_error::<T>("FromWorld"))
} else {
panic!(
"the `ReflectFromWorld` registration for `{}` produced a value of a different type",
"Couldn't create an instance of `{}` using the reflected `FromReflect`, \
`Default` or `FromWorld` traits. Are you perhaps missing a `#[reflect(Default)]` \
or `#[reflect(FromWorld)]`?",
// FIXME: once we have unique reflect, use `TypePath`.
std::any::type_name::<T>(),
);
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_ecs/src/reflect/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
};
use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};

use super::from_reflect_or_world;
use super::from_reflect_with_fallback;

/// A struct used to operate on reflected [`Resource`] of a type.
///
Expand Down Expand Up @@ -180,7 +180,7 @@ impl<R: Resource + FromReflect> FromType<R> for ReflectResource {
fn from_type() -> Self {
ReflectResource(ReflectResourceFns {
insert: |world, reflected_resource, registry| {
let resource = from_reflect_or_world::<R>(reflected_resource, world, registry);
let resource = from_reflect_with_fallback::<R>(reflected_resource, world, registry);
world.insert_resource(resource);
},
apply: |world, reflected_resource| {
Expand All @@ -191,7 +191,8 @@ impl<R: Resource + FromReflect> FromType<R> for ReflectResource {
if let Some(mut resource) = world.get_resource_mut::<R>() {
resource.apply(reflected_resource);
} else {
let resource = from_reflect_or_world::<R>(reflected_resource, world, registry);
let resource =
from_reflect_with_fallback::<R>(reflected_resource, world, registry);
world.insert_resource(resource);
}
},
Expand All @@ -212,7 +213,7 @@ impl<R: Resource + FromReflect> FromType<R> for ReflectResource {
copy: |source_world, destination_world, registry| {
let source_resource = source_world.resource::<R>();
let destination_resource =
from_reflect_or_world::<R>(source_resource, destination_world, registry);
from_reflect_with_fallback::<R>(source_resource, destination_world, registry);
destination_world.insert_resource(destination_resource);
},
})
Expand Down

0 comments on commit d9b6973

Please sign in to comment.