@@ -19,7 +19,7 @@ use bevy_ecs::{
19
19
system:: { Res , ResMut } ,
20
20
} ;
21
21
use bevy_platform:: collections:: HashMap ;
22
- use bevy_reflect:: { prelude:: ReflectDefault , Reflect , ReflectSerialize } ;
22
+ use bevy_reflect:: { prelude:: ReflectDefault , Reflect } ;
23
23
use derive_more:: derive:: From ;
24
24
use petgraph:: {
25
25
graph:: { DiGraph , NodeIndex } ,
@@ -29,6 +29,7 @@ use ron::de::SpannedError;
29
29
use serde:: { Deserialize , Serialize } ;
30
30
use smallvec:: SmallVec ;
31
31
use thiserror:: Error ;
32
+ use tracing:: warn;
32
33
33
34
use crate :: { AnimationClip , AnimationTargetId } ;
34
35
@@ -108,9 +109,8 @@ use crate::{AnimationClip, AnimationTargetId};
108
109
/// [RON]: https://github.com/ron-rs/ron
109
110
///
110
111
/// [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md
111
- #[ derive( Asset , Reflect , Clone , Debug , Serialize ) ]
112
- #[ reflect( Serialize , Debug , Clone ) ]
113
- #[ serde( into = "SerializedAnimationGraph" ) ]
112
+ #[ derive( Asset , Reflect , Clone , Debug ) ]
113
+ #[ reflect( Debug , Clone ) ]
114
114
pub struct AnimationGraph {
115
115
/// The `petgraph` data structure that defines the animation graph.
116
116
pub graph : AnimationDiGraph ,
@@ -242,20 +242,40 @@ pub enum AnimationNodeType {
242
242
#[ derive( Default ) ]
243
243
pub struct AnimationGraphAssetLoader ;
244
244
245
- /// Various errors that can occur when serializing or deserializing animation
246
- /// graphs to and from RON, respectively.
245
+ /// Errors that can occur when serializing animation graphs to RON.
246
+ #[ derive( Error , Debug ) ]
247
+ pub enum AnimationGraphSaveError {
248
+ /// An I/O error occurred.
249
+ #[ error( transparent) ]
250
+ Io ( #[ from] io:: Error ) ,
251
+ /// An error occurred in RON serialization.
252
+ #[ error( transparent) ]
253
+ Ron ( #[ from] ron:: Error ) ,
254
+ /// An error occurred converting the graph to its serialization form.
255
+ #[ error( transparent) ]
256
+ ConvertToSerialized ( #[ from] NonPathHandleError ) ,
257
+ }
258
+
259
+ /// Errors that can occur when deserializing animation graphs from RON.
247
260
#[ derive( Error , Debug ) ]
248
261
pub enum AnimationGraphLoadError {
249
262
/// An I/O error occurred.
250
- #[ error( "I/O" ) ]
263
+ #[ error( transparent ) ]
251
264
Io ( #[ from] io:: Error ) ,
252
- /// An error occurred in RON serialization or deserialization.
253
- #[ error( "RON serialization" ) ]
265
+ /// An error occurred in RON deserialization.
266
+ #[ error( transparent ) ]
254
267
Ron ( #[ from] ron:: Error ) ,
255
268
/// An error occurred in RON deserialization, and the location of the error
256
269
/// is supplied.
257
- #[ error( "RON serialization" ) ]
270
+ #[ error( transparent ) ]
258
271
SpannedRon ( #[ from] SpannedError ) ,
272
+ /// The deserialized graph contained legacy data that we no longer support.
273
+ #[ error(
274
+ "The deserialized AnimationGraph contained an AnimationClip referenced by an AssetId, \
275
+ which is no longer supported. Consider manually deserializing the SerializedAnimationGraph \
276
+ type and determine how to migrate any SerializedAnimationClip::AssetId animation clips"
277
+ ) ]
278
+ GraphContainsLegacyAssetId ,
259
279
}
260
280
261
281
/// Acceleration structures for animation graphs that allows Bevy to evaluate
@@ -388,18 +408,32 @@ pub struct SerializedAnimationGraphNode {
388
408
#[ derive( Serialize , Deserialize ) ]
389
409
pub enum SerializedAnimationNodeType {
390
410
/// Corresponds to [`AnimationNodeType::Clip`].
391
- Clip ( SerializedAnimationClip ) ,
411
+ Clip ( MigrationSerializedAnimationClip ) ,
392
412
/// Corresponds to [`AnimationNodeType::Blend`].
393
413
Blend ,
394
414
/// Corresponds to [`AnimationNodeType::Add`].
395
415
Add ,
396
416
}
397
417
398
- /// A version of `Handle<AnimationClip>` suitable for serializing as an asset.
418
+ /// A type to facilitate migration from the legacy format of [`SerializedAnimationGraph`] to the
419
+ /// new format.
399
420
///
400
- /// This replaces any handle that has a path with an [`AssetPath`]. Failing
401
- /// that, the asset ID is serialized directly.
421
+ /// By using untagged serde deserialization, we can try to deserialize the modern form, then
422
+ /// fallback to the legacy form. Users must migrate to the modern form by Bevy 0.18.
423
+ // TODO: Delete this after Bevy 0.17.
402
424
#[ derive( Serialize , Deserialize ) ]
425
+ #[ serde( untagged) ]
426
+ pub enum MigrationSerializedAnimationClip {
427
+ /// This is the new type of this field.
428
+ Modern ( AssetPath < ' static > ) ,
429
+ /// This is the legacy type of this field. Users must migrate away from this.
430
+ #[ serde( skip_serializing) ]
431
+ Legacy ( SerializedAnimationClip ) ,
432
+ }
433
+
434
+ /// The legacy form of serialized animation clips. This allows raw asset IDs to be deserialized.
435
+ // TODO: Delete this after Bevy 0.17.
436
+ #[ derive( Deserialize ) ]
403
437
pub enum SerializedAnimationClip {
404
438
/// Records an asset path.
405
439
AssetPath ( AssetPath < ' static > ) ,
@@ -648,12 +682,13 @@ impl AnimationGraph {
648
682
///
649
683
/// If writing to a file, it can later be loaded with the
650
684
/// [`AnimationGraphAssetLoader`] to reconstruct the graph.
651
- pub fn save < W > ( & self , writer : & mut W ) -> Result < ( ) , AnimationGraphLoadError >
685
+ pub fn save < W > ( & self , writer : & mut W ) -> Result < ( ) , AnimationGraphSaveError >
652
686
where
653
687
W : Write ,
654
688
{
655
689
let mut ron_serializer = ron:: ser:: Serializer :: new ( writer, None ) ?;
656
- Ok ( self . serialize ( & mut ron_serializer) ?)
690
+ let serialized_graph: SerializedAnimationGraph = self . clone ( ) . try_into ( ) ?;
691
+ Ok ( serialized_graph. serialize ( & mut ron_serializer) ?)
657
692
}
658
693
659
694
/// Adds an animation target (bone) to the mask group with the given ID.
@@ -758,28 +793,55 @@ impl AssetLoader for AnimationGraphAssetLoader {
758
793
let serialized_animation_graph = SerializedAnimationGraph :: deserialize ( & mut deserializer)
759
794
. map_err ( |err| deserializer. span_error ( err) ) ?;
760
795
761
- // Load all `AssetPath`s to convert from a
762
- // `SerializedAnimationGraph` to a real `AnimationGraph`.
763
- Ok ( AnimationGraph {
764
- graph : serialized_animation_graph. graph . map (
765
- |_, serialized_node| AnimationGraphNode {
766
- node_type : match serialized_node. node_type {
767
- SerializedAnimationNodeType :: Clip ( ref clip) => match clip {
768
- SerializedAnimationClip :: AssetId ( asset_id) => {
769
- AnimationNodeType :: Clip ( Handle :: Weak ( * asset_id) )
770
- }
771
- SerializedAnimationClip :: AssetPath ( asset_path) => {
772
- AnimationNodeType :: Clip ( load_context. load ( asset_path) )
796
+ // Load all `AssetPath`s to convert from a `SerializedAnimationGraph` to a real
797
+ // `AnimationGraph`. This is effectively a `DiGraph::map`, but this allows us to return
798
+ // errors.
799
+ let mut animation_graph = DiGraph :: with_capacity (
800
+ serialized_animation_graph. graph . node_count ( ) ,
801
+ serialized_animation_graph. graph . edge_count ( ) ,
802
+ ) ;
803
+
804
+ let mut already_warned = false ;
805
+ for serialized_node in serialized_animation_graph. graph . node_weights ( ) {
806
+ animation_graph. add_node ( AnimationGraphNode {
807
+ node_type : match serialized_node. node_type {
808
+ SerializedAnimationNodeType :: Clip ( ref clip) => match clip {
809
+ MigrationSerializedAnimationClip :: Modern ( path) => {
810
+ AnimationNodeType :: Clip ( load_context. load ( path. clone ( ) ) )
811
+ }
812
+ MigrationSerializedAnimationClip :: Legacy (
813
+ SerializedAnimationClip :: AssetPath ( path) ,
814
+ ) => {
815
+ if !already_warned {
816
+ let path = load_context. asset_path ( ) ;
817
+ warn ! (
818
+ "Loaded an AnimationGraph asset at \" {path}\" which contains a \
819
+ legacy-style SerializedAnimationClip. Please re-save the asset \
820
+ using AnimationGraph::save to automatically migrate to the new \
821
+ format"
822
+ ) ;
823
+ already_warned = true ;
773
824
}
774
- } ,
775
- SerializedAnimationNodeType :: Blend => AnimationNodeType :: Blend ,
776
- SerializedAnimationNodeType :: Add => AnimationNodeType :: Add ,
825
+ AnimationNodeType :: Clip ( load_context. load ( path. clone ( ) ) )
826
+ }
827
+ MigrationSerializedAnimationClip :: Legacy (
828
+ SerializedAnimationClip :: AssetId ( _) ,
829
+ ) => {
830
+ return Err ( AnimationGraphLoadError :: GraphContainsLegacyAssetId ) ;
831
+ }
777
832
} ,
778
- mask : serialized_node . mask ,
779
- weight : serialized_node . weight ,
833
+ SerializedAnimationNodeType :: Blend => AnimationNodeType :: Blend ,
834
+ SerializedAnimationNodeType :: Add => AnimationNodeType :: Add ,
780
835
} ,
781
- |_, _| ( ) ,
782
- ) ,
836
+ mask : serialized_node. mask ,
837
+ weight : serialized_node. weight ,
838
+ } ) ;
839
+ }
840
+ for edge in serialized_animation_graph. graph . raw_edges ( ) {
841
+ animation_graph. add_edge ( edge. source ( ) , edge. target ( ) , ( ) ) ;
842
+ }
843
+ Ok ( AnimationGraph {
844
+ graph : animation_graph,
783
845
root : serialized_animation_graph. root ,
784
846
mask_groups : serialized_animation_graph. mask_groups ,
785
847
} )
@@ -790,37 +852,50 @@ impl AssetLoader for AnimationGraphAssetLoader {
790
852
}
791
853
}
792
854
793
- impl From < AnimationGraph > for SerializedAnimationGraph {
794
- fn from ( animation_graph : AnimationGraph ) -> Self {
795
- // If any of the animation clips have paths, then serialize them as
796
- // `SerializedAnimationClip::AssetPath` so that the
797
- // `AnimationGraphAssetLoader` can load them.
798
- Self {
799
- graph : animation_graph. graph . map (
800
- |_, node| SerializedAnimationGraphNode {
801
- weight : node. weight ,
802
- mask : node. mask ,
803
- node_type : match node. node_type {
804
- AnimationNodeType :: Clip ( ref clip) => match clip. path ( ) {
805
- Some ( path) => SerializedAnimationNodeType :: Clip (
806
- SerializedAnimationClip :: AssetPath ( path. clone ( ) ) ,
807
- ) ,
808
- None => SerializedAnimationNodeType :: Clip (
809
- SerializedAnimationClip :: AssetId ( clip. id ( ) ) ,
810
- ) ,
811
- } ,
812
- AnimationNodeType :: Blend => SerializedAnimationNodeType :: Blend ,
813
- AnimationNodeType :: Add => SerializedAnimationNodeType :: Add ,
855
+ impl TryFrom < AnimationGraph > for SerializedAnimationGraph {
856
+ type Error = NonPathHandleError ;
857
+
858
+ fn try_from ( animation_graph : AnimationGraph ) -> Result < Self , NonPathHandleError > {
859
+ // Convert all the `Handle<AnimationClip>` to AssetPath, so that
860
+ // `AnimationGraphAssetLoader` can load them. This is effectively just doing a
861
+ // `DiGraph::map`, except we need to return an error if any handles aren't associated to a
862
+ // path.
863
+ let mut serialized_graph = DiGraph :: with_capacity (
864
+ animation_graph. graph . node_count ( ) ,
865
+ animation_graph. graph . edge_count ( ) ,
866
+ ) ;
867
+ for node in animation_graph. graph . node_weights ( ) {
868
+ serialized_graph. add_node ( SerializedAnimationGraphNode {
869
+ weight : node. weight ,
870
+ mask : node. mask ,
871
+ node_type : match node. node_type {
872
+ AnimationNodeType :: Clip ( ref clip) => match clip. path ( ) {
873
+ Some ( path) => SerializedAnimationNodeType :: Clip (
874
+ MigrationSerializedAnimationClip :: Modern ( path. clone ( ) ) ,
875
+ ) ,
876
+ None => return Err ( NonPathHandleError ) ,
814
877
} ,
878
+ AnimationNodeType :: Blend => SerializedAnimationNodeType :: Blend ,
879
+ AnimationNodeType :: Add => SerializedAnimationNodeType :: Add ,
815
880
} ,
816
- |_, _| ( ) ,
817
- ) ,
881
+ } ) ;
882
+ }
883
+ for edge in animation_graph. graph . raw_edges ( ) {
884
+ serialized_graph. add_edge ( edge. source ( ) , edge. target ( ) , ( ) ) ;
885
+ }
886
+ Ok ( Self {
887
+ graph : serialized_graph,
818
888
root : animation_graph. root ,
819
889
mask_groups : animation_graph. mask_groups ,
820
- }
890
+ } )
821
891
}
822
892
}
823
893
894
+ /// Error for when only path [`Handle`]s are supported.
895
+ #[ derive( Error , Debug ) ]
896
+ #[ error( "AnimationGraph contains a handle to an AnimationClip that does not correspond to an asset path" ) ]
897
+ pub struct NonPathHandleError ;
898
+
824
899
/// A system that creates, updates, and removes [`ThreadedAnimationGraph`]
825
900
/// structures for every changed [`AnimationGraph`].
826
901
///
0 commit comments