1+ use godot:: global:: PropertyUsageFlags ;
12use godot:: prelude:: * ;
23use half:: f16;
4+ use std:: collections:: hash_map:: DefaultHasher ;
35use std:: fmt:: Display ;
6+ use std:: hash:: { Hash , Hasher } ;
47
58#[ derive( GodotClass ) ]
69/// Streaming helper around `PackedByteArray` for binary serialization from Godot.
@@ -16,6 +19,27 @@ pub struct BinaryStream {
1619 last_error : String ,
1720}
1821
22+ // A helper struct to hold processed property information.
23+ // We derive Ord to enable sorting by name.
24+ #[ derive( Debug , Clone , Eq , PartialEq ) ]
25+ struct StorableProperty {
26+ name : GString ,
27+ type_ : VariantType ,
28+ }
29+
30+ // Manual implementation of Ord to sort by the string representation of the name.
31+ impl Ord for StorableProperty {
32+ fn cmp ( & self , other : & Self ) -> std:: cmp:: Ordering {
33+ self . name . to_string ( ) . cmp ( & other. name . to_string ( ) )
34+ }
35+ }
36+
37+ impl PartialOrd for StorableProperty {
38+ fn partial_cmp ( & self , other : & Self ) -> Option < std:: cmp:: Ordering > {
39+ Some ( self . cmp ( other) )
40+ }
41+ }
42+
1943impl BinaryStream {
2044 fn set_error ( & mut self , msg : impl Into < String > ) {
2145 let text = msg. into ( ) ;
@@ -503,6 +527,161 @@ impl BinaryStream {
503527 }
504528 Some ( data)
505529 }
530+
531+ /// Checks if a variant type is supported for serialization by `write_variant`.
532+ fn is_type_supported ( & self , type_ : VariantType ) -> bool {
533+ matches ! (
534+ type_,
535+ VariantType :: BOOL
536+ | VariantType :: INT
537+ | VariantType :: FLOAT
538+ | VariantType :: STRING
539+ | VariantType :: VECTOR2
540+ | VariantType :: VECTOR2I
541+ | VariantType :: RECT2
542+ | VariantType :: RECT2I
543+ | VariantType :: VECTOR3
544+ | VariantType :: VECTOR3I
545+ | VariantType :: TRANSFORM2D
546+ | VariantType :: VECTOR4
547+ | VariantType :: VECTOR4I
548+ | VariantType :: PLANE
549+ | VariantType :: QUATERNION
550+ | VariantType :: AABB
551+ | VariantType :: BASIS
552+ | VariantType :: TRANSFORM3D
553+ | VariantType :: PROJECTION
554+ | VariantType :: COLOR
555+ | VariantType :: STRING_NAME
556+ | VariantType :: NODE_PATH
557+ | VariantType :: RID
558+ | VariantType :: PACKED_BYTE_ARRAY
559+ | VariantType :: PACKED_INT32_ARRAY
560+ | VariantType :: PACKED_INT64_ARRAY
561+ | VariantType :: PACKED_FLOAT32_ARRAY
562+ | VariantType :: PACKED_FLOAT64_ARRAY
563+ | VariantType :: PACKED_STRING_ARRAY
564+ | VariantType :: PACKED_VECTOR2_ARRAY
565+ | VariantType :: PACKED_VECTOR3_ARRAY
566+ | VariantType :: PACKED_COLOR_ARRAY
567+ | VariantType :: PACKED_VECTOR4_ARRAY
568+ )
569+ }
570+
571+ /// Fetches, filters, and sorts the properties of an object that are marked for storage.
572+ fn get_storable_properties (
573+ & mut self ,
574+ object : & Gd < Object > ,
575+ caller : & str ,
576+ ) -> Option < Vec < StorableProperty > > {
577+ let prop_list = object. get_property_list ( ) ;
578+ let mut storable_props = Vec :: new ( ) ;
579+
580+ for prop_dict in prop_list. iter_shared ( ) {
581+ let Some ( name_var) = prop_dict. get ( "name" ) else {
582+ self . set_error ( format ! ( "{caller}: property dictionary missing 'name'" ) ) ;
583+ return None ;
584+ } ;
585+ let Some ( name) = name_var. try_to_relaxed :: < GString > ( ) . ok ( ) else {
586+ self . set_error ( format ! ( "{caller}: property 'name' was not a StringName" ) ) ;
587+ return None ;
588+ } ;
589+
590+ let Some ( type_var) = prop_dict. get ( "type" ) else {
591+ self . set_error ( format ! ( "{caller}: property '{name}' missing 'type'" ) ) ;
592+ return None ;
593+ } ;
594+ let Some ( type_int) = type_var. try_to_relaxed :: < i32 > ( ) . ok ( ) else {
595+ self . set_error ( format ! ( "{caller}: property '{name}' type was not an int" ) ) ;
596+ return None ;
597+ } ;
598+ let type_ = VariantType { ord : type_int } ;
599+
600+ let Some ( usage_var) = prop_dict. get ( "usage" ) else {
601+ self . set_error ( format ! ( "{caller}: property '{name}' missing 'usage'" ) ) ;
602+ return None ;
603+ } ;
604+ let Some ( usage_int) = usage_var. try_to :: < u64 > ( ) . ok ( ) else {
605+ self . set_error ( format ! ( "{caller}: property '{name}' usage was not an int" ) ) ;
606+ return None ;
607+ } ;
608+ let Some ( usage) = PropertyUsageFlags :: try_from_ord ( usage_int) else {
609+ self . set_error ( format ! (
610+ "{caller}: property '{name}' usage had invalid flags {usage_int}"
611+ ) ) ;
612+ return None ;
613+ } ;
614+
615+ if usage. is_set ( PropertyUsageFlags :: STORAGE ) && self . is_type_supported ( type_) {
616+ storable_props. push ( StorableProperty { name, type_ } ) ;
617+ }
618+ }
619+
620+ storable_props. sort ( ) ; // Sort alphabetically by property name
621+ Some ( storable_props)
622+ }
623+
624+ /// Computes a stable hash for a sorted list of storable properties.
625+ fn compute_property_hash ( properties : & [ StorableProperty ] ) -> u64 {
626+ let mut hasher = DefaultHasher :: new ( ) ;
627+ for prop in properties {
628+ prop. name . to_string ( ) . hash ( & mut hasher) ;
629+ ( prop. type_ ) . hash ( & mut hasher) ;
630+ }
631+ hasher. finish ( )
632+ }
633+
634+ /// Reads a variant from the stream based on an expected type.
635+ fn read_variant_by_type ( & mut self , type_ : VariantType ) -> Option < Variant > {
636+ let value = match type_ {
637+ VariantType :: BOOL => self . read_bool ( ) . to_variant ( ) ,
638+ VariantType :: INT => self . read_i64 ( ) . to_variant ( ) ,
639+ VariantType :: FLOAT => self . read_f64 ( ) . to_variant ( ) ,
640+ VariantType :: STRING => self . read_string ( ) . to_variant ( ) ,
641+ VariantType :: VECTOR2 => self . read_vector2 ( ) . to_variant ( ) ,
642+ VariantType :: VECTOR2I => self . read_vector2i ( ) . to_variant ( ) ,
643+ VariantType :: RECT2 => self . read_rect2 ( ) . to_variant ( ) ,
644+ VariantType :: RECT2I => self . read_rect2i ( ) . to_variant ( ) ,
645+ VariantType :: VECTOR3 => self . read_vector3 ( ) . to_variant ( ) ,
646+ VariantType :: VECTOR3I => self . read_vector3i ( ) . to_variant ( ) ,
647+ VariantType :: TRANSFORM2D => self . read_transform2d ( ) . to_variant ( ) ,
648+ VariantType :: VECTOR4 => self . read_vector4 ( ) . to_variant ( ) ,
649+ VariantType :: VECTOR4I => self . read_vector4i ( ) . to_variant ( ) ,
650+ VariantType :: PLANE => self . read_plane ( ) . to_variant ( ) ,
651+ VariantType :: QUATERNION => self . read_quaternion ( ) . to_variant ( ) ,
652+ VariantType :: AABB => self . read_aabb ( ) . to_variant ( ) ,
653+ VariantType :: BASIS => self . read_basis ( ) . to_variant ( ) ,
654+ VariantType :: TRANSFORM3D => self . read_transform3d ( ) . to_variant ( ) ,
655+ VariantType :: PROJECTION => self . read_projection ( ) . to_variant ( ) ,
656+ VariantType :: COLOR => self . read_color ( ) . to_variant ( ) ,
657+ VariantType :: STRING_NAME => self . read_string_name ( ) . to_variant ( ) ,
658+ VariantType :: NODE_PATH => self . read_node_path ( ) . to_variant ( ) ,
659+ VariantType :: RID => self . read_rid ( ) . to_variant ( ) ,
660+ VariantType :: PACKED_BYTE_ARRAY => self . read_packed_byte_array ( ) . to_variant ( ) ,
661+ VariantType :: PACKED_INT32_ARRAY => self . read_packed_int32_array ( ) . to_variant ( ) ,
662+ VariantType :: PACKED_INT64_ARRAY => self . read_packed_int64_array ( ) . to_variant ( ) ,
663+ VariantType :: PACKED_FLOAT32_ARRAY => self . read_packed_float32_array ( ) . to_variant ( ) ,
664+ VariantType :: PACKED_FLOAT64_ARRAY => self . read_packed_float64_array ( ) . to_variant ( ) ,
665+ VariantType :: PACKED_STRING_ARRAY => self . read_packed_string_array ( ) . to_variant ( ) ,
666+ VariantType :: PACKED_VECTOR2_ARRAY => self . read_packed_vector2_array ( ) . to_variant ( ) ,
667+ VariantType :: PACKED_VECTOR3_ARRAY => self . read_packed_vector3_array ( ) . to_variant ( ) ,
668+ VariantType :: PACKED_COLOR_ARRAY => self . read_packed_color_array ( ) . to_variant ( ) ,
669+ VariantType :: PACKED_VECTOR4_ARRAY => self . read_packed_vector4_array ( ) . to_variant ( ) ,
670+ _ => {
671+ self . set_error ( format ! (
672+ "read_variant_by_type: cannot read unsupported type '{:?}'" ,
673+ type_
674+ ) ) ;
675+ return None ;
676+ }
677+ } ;
678+ // Check if any read operations failed and set an error.
679+ if !self . last_error . is_empty ( ) {
680+ None
681+ } else {
682+ Some ( value)
683+ }
684+ }
506685}
507686
508687#[ godot_api]
@@ -1398,4 +1577,128 @@ impl BinaryStream {
13981577 . map ( PackedVector4Array :: from)
13991578 . unwrap_or_else ( PackedVector4Array :: new)
14001579 }
1580+
1581+ /// Writes a Godot `Variant` to the stream.
1582+ ///
1583+ /// This function checks the variant's type and calls the corresponding
1584+ /// `write_*` method. If the type is not supported for serialization,
1585+ /// it sets an error and returns `false`.
1586+ #[ func]
1587+ pub fn write_variant ( & mut self , value : Variant ) -> bool {
1588+ match value. get_type ( ) {
1589+ VariantType :: BOOL => self . write_bool ( value. to ( ) ) ,
1590+ VariantType :: INT => self . write_i64 ( value. to ( ) ) ,
1591+ VariantType :: FLOAT => self . write_f64 ( value. to ( ) ) ,
1592+ VariantType :: STRING => self . write_string ( value. to ( ) ) ,
1593+ VariantType :: VECTOR2 => self . write_vector2 ( value. to ( ) ) ,
1594+ VariantType :: VECTOR2I => self . write_vector2i ( value. to ( ) ) ,
1595+ VariantType :: RECT2 => self . write_rect2 ( value. to ( ) ) ,
1596+ VariantType :: RECT2I => self . write_rect2i ( value. to ( ) ) ,
1597+ VariantType :: VECTOR3 => self . write_vector3 ( value. to ( ) ) ,
1598+ VariantType :: VECTOR3I => self . write_vector3i ( value. to ( ) ) ,
1599+ VariantType :: TRANSFORM2D => self . write_transform2d ( value. to ( ) ) ,
1600+ VariantType :: VECTOR4 => self . write_vector4 ( value. to ( ) ) ,
1601+ VariantType :: VECTOR4I => self . write_vector4i ( value. to ( ) ) ,
1602+ VariantType :: PLANE => self . write_plane ( value. to ( ) ) ,
1603+ VariantType :: QUATERNION => self . write_quaternion ( value. to ( ) ) ,
1604+ VariantType :: AABB => self . write_aabb ( value. to ( ) ) ,
1605+ VariantType :: BASIS => self . write_basis ( value. to ( ) ) ,
1606+ VariantType :: TRANSFORM3D => self . write_transform3d ( value. to ( ) ) ,
1607+ VariantType :: PROJECTION => self . write_projection ( value. to ( ) ) ,
1608+ VariantType :: COLOR => self . write_color ( value. to ( ) ) ,
1609+ VariantType :: STRING_NAME => self . write_string_name ( value. to ( ) ) ,
1610+ VariantType :: NODE_PATH => self . write_node_path ( value. to ( ) ) ,
1611+ VariantType :: RID => self . write_rid ( value. to ( ) ) ,
1612+ VariantType :: PACKED_BYTE_ARRAY => self . write_packed_byte_array ( value. to ( ) ) ,
1613+ VariantType :: PACKED_INT32_ARRAY => self . write_packed_int32_array ( value. to ( ) ) ,
1614+ VariantType :: PACKED_INT64_ARRAY => self . write_packed_int64_array ( value. to ( ) ) ,
1615+ VariantType :: PACKED_FLOAT32_ARRAY => self . write_packed_float32_array ( value. to ( ) ) ,
1616+ VariantType :: PACKED_FLOAT64_ARRAY => self . write_packed_float64_array ( value. to ( ) ) ,
1617+ VariantType :: PACKED_STRING_ARRAY => self . write_packed_string_array ( value. to ( ) ) ,
1618+ VariantType :: PACKED_VECTOR2_ARRAY => self . write_packed_vector2_array ( value. to ( ) ) ,
1619+ VariantType :: PACKED_VECTOR3_ARRAY => self . write_packed_vector3_array ( value. to ( ) ) ,
1620+ VariantType :: PACKED_COLOR_ARRAY => self . write_packed_color_array ( value. to ( ) ) ,
1621+ VariantType :: PACKED_VECTOR4_ARRAY => self . write_packed_vector4_array ( value. to ( ) ) ,
1622+ _ => {
1623+ self . set_error ( format ! (
1624+ "write_variant: unsupported type '{:?}'" ,
1625+ value. get_type( )
1626+ ) ) ;
1627+ false
1628+ }
1629+ }
1630+ }
1631+
1632+ /// Serializes a Godot `Object`'s properties to the stream.
1633+ ///
1634+ /// It inspects the object's properties, filtering for those with the `STORAGE`
1635+ /// usage flag and a serializable type. It then writes a hash of the property
1636+ /// names and types, followed by the value of each property. This hash allows
1637+ /// `read_object` to verify that the data schema matches.
1638+ #[ func]
1639+ pub fn write_object ( & mut self , object : Gd < Object > ) -> bool {
1640+ let Some ( properties) = self . get_storable_properties ( & object, "write_object" ) else {
1641+ return false ;
1642+ } ;
1643+
1644+ let hash = Self :: compute_property_hash ( & properties) ;
1645+ if !self . write_fixed ( "write_object.hash" , hash. to_le_bytes ( ) ) {
1646+ return false ;
1647+ }
1648+
1649+ for prop in properties. iter ( ) {
1650+ let value = object. get ( prop. name . arg ( ) ) ;
1651+ if !self . write_variant ( value) {
1652+ // write_variant sets its own detailed error.
1653+ // We'll add context that it happened during object serialization.
1654+ let base_error = self . last_error . clone ( ) ;
1655+ self . set_error ( format ! (
1656+ "write_object: failed to write property '{}': {}" ,
1657+ prop. name, base_error
1658+ ) ) ;
1659+ return false ;
1660+ }
1661+ }
1662+ true
1663+ }
1664+
1665+ /// Deserializes data from the stream into an existing Godot `Object`.
1666+ ///
1667+ /// It first reads a schema hash and compares it to a hash generated from the
1668+ /// target object's storable properties. If they match, it proceeds to read
1669+ /// each property's value from the stream and sets it on the object. If the
1670+ /// hashes mismatch, an error is set and the object is not modified.
1671+ #[ func]
1672+ pub fn read_object ( & mut self , mut object : Gd < Object > ) -> bool {
1673+ let Some ( properties) = self . get_storable_properties ( & object, "read_object" ) else {
1674+ return false ;
1675+ } ;
1676+
1677+ let expected_hash = Self :: compute_property_hash ( & properties) ;
1678+
1679+ let Some ( stored_hash) = self . read_u64_inner ( "read_object.hash" ) else {
1680+ // read_u64_inner would have set a more specific error.
1681+ return false ;
1682+ } ;
1683+
1684+ if expected_hash != stored_hash {
1685+ self . set_error ( format ! (
1686+ "read_object: schema hash mismatch. Expected {expected_hash}, found {stored_hash}. The object's structure does not match the serialized data."
1687+ ) ) ;
1688+ return false ;
1689+ }
1690+
1691+ for prop in properties. iter ( ) {
1692+ let Some ( value) = self . read_variant_by_type ( prop. type_ ) else {
1693+ let base_error = self . last_error . clone ( ) ;
1694+ self . set_error ( format ! (
1695+ "read_object: failed to read property '{}': {}" ,
1696+ prop. name, base_error
1697+ ) ) ;
1698+ return false ;
1699+ } ;
1700+ object. set ( prop. name . arg ( ) , & value) ;
1701+ }
1702+ true
1703+ }
14011704}
0 commit comments