Skip to content

Commit 168d497

Browse files
committed
BinaryStream: Allow writing any variant, writing and reading objects
Still only supports the listed data types (primitives)
1 parent 2a38678 commit 168d497

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed

src/binary_stream.rs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use godot::global::PropertyUsageFlags;
12
use godot::prelude::*;
23
use half::f16;
4+
use std::collections::hash_map::DefaultHasher;
35
use 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+
1943
impl 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

Comments
 (0)