Skip to content

bevy_reflect: Convert dynamic types to concrete instances #4597

@MrGVSV

Description

@MrGVSV

What problem does this solve or what need does it fill?

TL;DR

Currently, there is not a good way to retrieve a concrete instance from a Dynamic*** type if the type is not known at compile-time. This is especially an issue in cases where a trait object is expected (and likely the only "known property" about the type).

The Issue

There are cases where a user or plugin author has a dynamic structure (we'll just use a DynamicStruct here for simplicity). They know that this DynamicStruct contains an object that implements— or likely implements— their custom trait SomeTrait.

They made their SomeTrait object-safe so it can be passed around as dyn SomeTrait, but the problem is that they don't know the underlying type. If they did, they might be able to convert this DynamicStruct into that type like:

let my_struct = <SomeStruct as FromReflect>::from_reflect(&dyn_struct);

But, again, we don't know SomeStruct exists.

Okay so maybe we just get the reflected trait from the registry:

let reflect_some_trait = type_registry
  .get_type_data::<ReflectSomeTrait>(concrete_type_id)
  .unwrap();

let my_struct: &dyn SomeTrait = reflect_some_trait.get(&dyn_struct).unwrap();

This seems to work, but that last line will panic. Why? It's because we're passing in our DynamicStruct instead of a concrete instance of SomeStruct.

So the question is, how do we get SomeStruct from DynamicStruct when we don't know SomeStruct exists?

Why?

It might seem obvious that if we need this functionality, just don't use DynamicStruct. However, this is not always possible. For example, deserialization using reflection will almost always return Dynamic*** data (for most types).

For plugin authors who might rely on some serialized data, this could be very important.

What solution would you like?

Ideally, a solution would meet at least some of the following (generic and obvious) criteria:

  1. Simple. The methods used to access the concrete type are easily approachable. The reflection crate is already a bit confusing and can be somewhat complex so the simpler, the better.
  2. Safe. The public API should be safe to use for those who maybe aren't as technically skilled (e.g. user-facing pointer shenanigans).
  3. Just Works. It would be nice if the system can work without the users needing to do anything on their part. As far as I can tell, this is probably not possible. But if it is, we should aim for it.

In regards to that last point, it would be great if we also didn't have to rely on FromReflect since not all types implement it. However, if the usefulness/ergonomics of it improves or it's added back into the Reflect derive, then it would be a lot nicer to use here.

If I had to give an example API, something like the following would probably be most ideal:

let my_struct: Box<dyn Reflect> = dyn_struct.convert();
// OR
let my_struct: Box<dyn Reflect> = dyn_struct.convert(&type_registry);

Obviously, this isn't a requirement and the actual API can look quite different.

What alternative(s) have you considered?

#4147 went about this by registering a new type: ReflectFromReflect.

When used on a struct as #[reflect(FromReflect)], it would be registered as type data for the type in the type registry. When retrieved, it could be used to convert the DynamicStruct to a reflected concrete instance:

#[derive(Reflect, FromReflect)]
#[reflect(FromReflect)]
struct SomeStruct { value: usize }

let rfr = registry.get_type_data::<ReflectFromReflect>(type_id).unwrap();
let my_struct: Box<dyn Reflect> = rfr.from_reflect(&dyn_struct).unwrap();

However, this was abandoned for a few reasons:

  • It adds boilerplate/confusion (we have to write FromReflect twice)
  • It relies on FromReflect which requires users derive it along with Reflect itself
  • It feels like we can do better

If this is the best we can do and/or the community prefers this method, it can be re-opened. However, for now it might be good to see what other solutions can be found.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ReflectionRuntime information about typesC-FeatureA new feature, making something new possible

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions