-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
This is a meta-issue for tracking the progress of improving how reflect-deserialized assets get loaded. This is a cross-cutting concern between both reflection and assets.
The motivating use case comes from bevy_animation_graph, where we deserialize AnimationGraphs from RON files. This looks like:
#[derive(Asset, Reflect)]
struct AnimationGraph {
pub nodes: Vec<AnimationNode>,
pub edges: (...),
}
#[derive(Reflect)]
struct AnimationNode {
pub name: String,
pub inner: Box<dyn NodeLike>, // NodeLike: Reflect
}
#[derive(Reflect)]
#[reflect(NodeLike)]
struct AnimationClipNode {
pub clip: Handle<AnimationClip>,
}
#[derive(Reflect)]
#[reflect(NodeLike)]
struct ChangeSpeedNode;
(
nodes: [
(
name: "Walk clip",
ty: "bevy_animation_graph::node::AnimationClipNode",
inner: (
clip: "animation_clips/walk.animclip.ron",
)
),
(
name: "Change speed",
ty: "bevy_animation_graph::node::ChangeSpeedNode",
inner: (),
),
],
edges: ( ... ),
)The two important things here are:
AnimationNodestores a type-erasedNodeLikeAnimationClipNode, and other node types, may store asset handles
However, it's currently very annoying to write code to deserialize and load this asset properly. Why?
AnimationNodecan't deriveReflectbecauseBox<dyn NodeLike>isn'tReflectbevy_reflect: AddReflectBoxto remotely reflectBox#14776- bevy_reflect: Add casting traits to support
Boxand other wrapper types #15532 - This means you have to write a manual
impl Reflect for AnimationNode
- We need a way to read the value of that
tyfield, and deserializeinnerbased on theTypeRegistrationwe look up- This is a perfect candidate for
DeserializeFromRegistry - bevy_reflect: Add
DeserializeWithRegistryandSerializeWithRegistry#8611
- This is a perfect candidate for
- When
Handle<T>is deserialized, it gets set toHandle::defaultwhich is useless- In the
AssetLoader::loadcall, we get aLoadContext- we should use that to actually kick off a load for this asset handle when we encounter it - bevy_reflect: Add
ReflectDeserializerProcessor#15482 - Note: the difference between what we're doing here and what we're doing in the step above, is that in this step, we have access to a
&mut LoadContextwhich we only get inside the asset processor. It doesn't exist in theTypeRegistryused byDeserializeFromRegistry.
- In the
- Using the
LoadContext/NestedLoader, we can't load a dynamic-typed asset (since we're using reflection, we do know theTin theHandle<T>, but only at runtime - i.e. only itsTypeId) - There should be an inbuilt impl of
ReflectDeserializerProcessorwhich accepts aLoadContextand does the two above steps for you- PR to be written
You have to write boilerplateDeserializeSeedcode for custom deserialization of anAnimationNodebecause of thattyHonestly not sure what to do for this. The customDeserializeSeedactually isn't too bad compared to the rest of the boilerplate that you have to write, but I'm sure there is a better way to do this.- The better way of doing this is using
DeserializeFromRegistryfrom bevy_reflect: AddDeserializeWithRegistryandSerializeWithRegistry#8611
- Also have to be able to save this kind of asset back to disk, serializing
Handle<T>s as just string asset paths
If these issues are solved, we have a much more powerful way to easily define these kinds of complex assets which depend on other assets, and that also use reflection to deserialize values of unknown types.
Once these are resolved, I would like to write an example which deserializes an asset in this reflective way, showing off the automatic recursive Handle loading, and deserializing type-erased Box<dyn Reflect>s.