diff --git a/gdnative-core/Cargo.toml b/gdnative-core/Cargo.toml index e9c0b0349..be58edb1c 100644 --- a/gdnative-core/Cargo.toml +++ b/gdnative-core/Cargo.toml @@ -24,6 +24,7 @@ glam = "0.18.0" indexmap = "1.7.0" ahash = "0.7.4" once_cell = "1.8.0" +serde = { version = "1", features = ["derive"], optional = true } gdnative-impl-proc-macros = { path = "../impl/proc_macros", version = "=0.9.3" } diff --git a/gdnative-core/src/core_types/color.rs b/gdnative-core/src/core_types/color.rs index 902ca5616..97e8e55c3 100644 --- a/gdnative-core/src/core_types/color.rs +++ b/gdnative-core/src/core_types/color.rs @@ -6,6 +6,7 @@ use crate::core_types::GodotString; /// RGBA color with 32 bits floating point components. #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Color { pub r: f32, pub g: f32, diff --git a/gdnative-core/src/core_types/error.rs b/gdnative-core/src/core_types/error.rs index c008dc0bb..d7ade9a44 100644 --- a/gdnative-core/src/core_types/error.rs +++ b/gdnative-core/src/core_types/error.rs @@ -2,6 +2,7 @@ use crate::sys; /// Error codes used in various Godot APIs. #[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(u32)] pub enum GodotError { Failed = sys::godot_error_GODOT_FAILED as u32, diff --git a/gdnative-core/src/core_types/geom/aabb.rs b/gdnative-core/src/core_types/geom/aabb.rs index 0f3508c19..0beafd202 100644 --- a/gdnative-core/src/core_types/geom/aabb.rs +++ b/gdnative-core/src/core_types/geom/aabb.rs @@ -3,6 +3,7 @@ use crate::core_types::Vector3; /// Axis-aligned bounding box. #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Aabb { pub position: Vector3, pub size: Vector3, diff --git a/gdnative-core/src/core_types/geom/basis.rs b/gdnative-core/src/core_types/geom/basis.rs index 9f8a2a311..e5b0937fa 100644 --- a/gdnative-core/src/core_types/geom/basis.rs +++ b/gdnative-core/src/core_types/geom/basis.rs @@ -5,6 +5,7 @@ use glam::Mat3; /// A 3x3 matrix. #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Basis { pub elements: [Vector3; 3], } diff --git a/gdnative-core/src/core_types/geom/plane.rs b/gdnative-core/src/core_types/geom/plane.rs index eec423f9c..19e3d8a98 100644 --- a/gdnative-core/src/core_types/geom/plane.rs +++ b/gdnative-core/src/core_types/geom/plane.rs @@ -3,6 +3,7 @@ use crate::core_types::{IsEqualApprox, Vector3}; /// Plane in hessian form. #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Plane { pub normal: Vector3, pub d: f32, diff --git a/gdnative-core/src/core_types/geom/transform.rs b/gdnative-core/src/core_types/geom/transform.rs index 251459230..6143f87c4 100644 --- a/gdnative-core/src/core_types/geom/transform.rs +++ b/gdnative-core/src/core_types/geom/transform.rs @@ -3,6 +3,7 @@ use crate::core_types::{Basis, Vector3}; /// 3D Transformation (3x4 matrix) Using basis + origin representation. #[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Transform { /// The basis is a matrix containing 3 Vector3 as its columns: X axis, Y axis, and Z axis. /// These vectors can be interpreted as the basis vectors of local coordinate system diff --git a/gdnative-core/src/core_types/mod.rs b/gdnative-core/src/core_types/mod.rs index 72a3ebca5..c610a5633 100644 --- a/gdnative-core/src/core_types/mod.rs +++ b/gdnative-core/src/core_types/mod.rs @@ -43,7 +43,7 @@ pub use rid::*; pub use string::*; pub use string_array::*; pub use transform2d::*; -pub use typed_array::TypedArray; +pub use typed_array::{Element, TypedArray}; pub use variant::*; pub use variant_array::*; pub use vector2::*; diff --git a/gdnative-core/src/core_types/node_path.rs b/gdnative-core/src/core_types/node_path.rs index 438c92e1f..0de66669a 100644 --- a/gdnative-core/src/core_types/node_path.rs +++ b/gdnative-core/src/core_types/node_path.rs @@ -176,3 +176,60 @@ impl fmt::Debug for NodePath { write!(f, "NodePath({})", self.to_string()) } } + +#[cfg(feature = "serde")] +mod serialize { + use super::*; + use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + + impl Serialize for NodePath { + #[inline] + fn serialize(&self, ser: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + ser.serialize_newtype_struct("NodePath", &*self.to_string()) + } + } + + impl<'de> Deserialize<'de> for NodePath { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NodePathVisitor; + + impl<'de> Visitor<'de> for NodePathVisitor { + type Value = NodePath; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a NodePath") + } + + fn visit_str(self, s: &str) -> Result + where + E: Error, + { + Ok(NodePath::from_str(s)) + } + + fn visit_newtype_struct( + self, + deserializer: D, + ) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(self) + } + } + + deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor) + } + } +} diff --git a/gdnative-core/src/core_types/quat.rs b/gdnative-core/src/core_types/quat.rs index f21064349..52eab1f16 100644 --- a/gdnative-core/src/core_types/quat.rs +++ b/gdnative-core/src/core_types/quat.rs @@ -3,6 +3,7 @@ use glam::EulerRot; use std::ops::{Mul, Neg}; #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Quat { pub x: f32, diff --git a/gdnative-core/src/core_types/rect2.rs b/gdnative-core/src/core_types/rect2.rs index 1fe015688..a69940d9b 100644 --- a/gdnative-core/src/core_types/rect2.rs +++ b/gdnative-core/src/core_types/rect2.rs @@ -1,5 +1,7 @@ use super::Vector2; +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Rect2 { pub position: Vector2, diff --git a/gdnative-core/src/core_types/string.rs b/gdnative-core/src/core_types/string.rs index a44ac8333..422625de5 100644 --- a/gdnative-core/src/core_types/string.rs +++ b/gdnative-core/src/core_types/string.rs @@ -612,6 +612,56 @@ where } } +#[cfg(feature = "serde")] +mod serialize { + use super::*; + use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + + impl Serialize for GodotString { + #[inline] + fn serialize( + &self, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: Serializer, + { + serializer.serialize_str(&*self.to_string()) + } + } + + #[cfg(feature = "serde")] + impl<'de> serialize::Deserialize<'de> for GodotString { + #[inline] + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct GodotStringVisitor; + impl<'de> Visitor<'de> for GodotStringVisitor { + type Value = GodotString; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a GodotString") + } + + fn visit_str(self, s: &str) -> Result + where + E: Error, + { + Ok(GodotString::from(s)) + } + } + + deserializer.deserialize_str(GodotStringVisitor) + } + } +} + godot_test!(test_string { use crate::core_types::{GodotString, Variant, VariantType, ToVariant}; diff --git a/gdnative-core/src/core_types/transform2d.rs b/gdnative-core/src/core_types/transform2d.rs index a8067908e..269ff54a1 100644 --- a/gdnative-core/src/core_types/transform2d.rs +++ b/gdnative-core/src/core_types/transform2d.rs @@ -1,5 +1,7 @@ use super::Vector2; +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Transform2D { pub x: Vector2, diff --git a/gdnative-core/src/core_types/typed_array.rs b/gdnative-core/src/core_types/typed_array.rs index c5b2a822b..0f4da8d34 100644 --- a/gdnative-core/src/core_types/typed_array.rs +++ b/gdnative-core/src/core_types/typed_array.rs @@ -483,3 +483,63 @@ macros::impl_typed_array_element! { mod private { pub trait Sealed {} } + +#[cfg(feature = "serde")] +mod serialize { + use super::*; + use serde::{ + de::{SeqAccess, Visitor}, + ser::SerializeSeq, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + use std::marker::PhantomData; + + impl Serialize for TypedArray { + #[inline] + fn serialize(&self, ser: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + let read = self.read(); + let mut ser = ser.serialize_seq(Some(read.len()))?; + for e in read.iter() { + ser.serialize_element(e)? + } + ser.end() + } + } + + impl<'de, T: Deserialize<'de> + Element> Deserialize<'de> for TypedArray { + #[inline] + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct TypedArrayVisitor(PhantomData); + impl<'de, T: Deserialize<'de> + Element> Visitor<'de> for TypedArrayVisitor { + type Value = TypedArray; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str(std::any::type_name::()) + } + + fn visit_seq( + self, + mut seq: A, + ) -> Result>::Error> + where + A: SeqAccess<'de>, + { + let mut vec = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); + while let Some(val) = seq.next_element::()? { + vec.push(val); + } + Ok(Self::Value::from_vec(vec)) + } + } + + deserializer.deserialize_seq(TypedArrayVisitor::(PhantomData)) + } + } +} diff --git a/gdnative-core/src/core_types/variant.rs b/gdnative-core/src/core_types/variant.rs index 97919faab..4d4ec0e66 100644 --- a/gdnative-core/src/core_types/variant.rs +++ b/gdnative-core/src/core_types/variant.rs @@ -9,6 +9,9 @@ use crate::object::*; use crate::private::{get_api, ManuallyManagedClassPlaceholder}; use crate::thread_access::*; +#[cfg(feature = "serde")] +mod serialize; + // TODO: implement Debug, PartialEq, etc. /// A `Variant` can represent many of godot's core types. @@ -109,6 +112,13 @@ macro_rules! decl_variant_type { )* } + impl VariantType { + /// The potential names of VariantTypes. Mostly used for serialization. + pub const NAMES: &'static [&'static str] = &[ + $(stringify!($variant),)* + ]; + } + /// Rust enum associating each primitive variant type to its value. /// /// For `Variant`s containing objects, the original `Variant` is returned unchanged, due to @@ -132,6 +142,19 @@ macro_rules! decl_variant_type { } } } + + impl<'a> From<&'a VariantDispatch> for Variant { + #[inline] + fn from(v: &'a VariantDispatch) -> Self { + match v { + $($(VariantDispatch::$variant(v) => { + let v: &$inner = v; + v.to_variant() + })?)* + _ => Variant::new() + } + } + } } } @@ -173,6 +196,15 @@ impl VariantType { pub fn from_sys(v: sys::godot_variant_type) -> VariantType { unsafe { transmute(v as u32) } } + + /// The `stringify!` representation of this variant. Mostly used for serialization. + #[inline] + pub const fn name(self) -> &'static str { + // NOTE: this assumes that the discriminants remain sequential, since any additions to the + // VariantType enum would require a breaking change anyway since it is not marked as non-exhaustive. + // See also the Deserialize implementation in the serde submodule. + Self::NAMES[self as usize] + } } #[repr(u32)] diff --git a/gdnative-core/src/core_types/variant/serialize.rs b/gdnative-core/src/core_types/variant/serialize.rs new file mode 100644 index 000000000..ef318464b --- /dev/null +++ b/gdnative-core/src/core_types/variant/serialize.rs @@ -0,0 +1,283 @@ +use super::*; +use serde::{ + de::{EnumAccess, Error, SeqAccess, VariantAccess, Visitor}, + ser::{Error as _, SerializeSeq}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::fmt::Formatter; + +/// Custom implementation to allow using the same visitor for VariantType as well as the discriminant +/// of VariantDispatch. +struct VariantTypeVisitor; + +impl<'de> Visitor<'de> for VariantTypeVisitor { + type Value = VariantType; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a VariantType") + } + + #[inline] + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + if value < VariantType::NAMES.len() as u64 { + Ok(VariantType::from_sys(value as sys::godot_variant_type)) + } else { + Err(E::custom(&*format!("invalid VariantType value: {}", value))) + } + } + + #[inline] + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + for (i, &name) in VariantType::NAMES.iter().enumerate() { + if name == value { + return Ok(VariantType::from_sys(i as sys::godot_variant_type)); + } + } + Err(E::custom(&*format!( + "invalid VariantType value: {:?}", + value + ))) + } + + #[inline] + fn visit_bytes(self, value: &[u8]) -> Result + where + E: Error, + { + for (i, &name) in VariantType::NAMES.iter().enumerate() { + if name.as_bytes() == value { + return Ok(VariantType::from_sys(i as sys::godot_variant_type)); + } + } + Err(E::custom(&*format!( + "invalid VariantType value: {:?}", + value + ))) + } + + #[inline] + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + let (t, v) = data.variant::()?; + v.unit_variant()?; + Ok(t) + } +} + +impl<'de> Deserialize<'de> for VariantType { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // NOTE: serde assumes that serialized indices correspond to the indices in the NAMES array. + // If any non-sequential VariantType values were added in the future, this could break, but + // that seems extremely unlikely, and would require a breaking change to godot-rust anyway + // since VariantType is not marked as non-exhaustive. + deserializer.deserialize_enum("VariantType", VariantType::NAMES, VariantTypeVisitor) + } +} + +/// Enables calling `deserialize_identifier` instead of `deserialize_enum` when deserializing VariantDispatch. +struct VariantDispatchDiscriminant(VariantType); + +impl<'de> Deserialize<'de> for VariantDispatchDiscriminant { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer + .deserialize_identifier(VariantTypeVisitor) + .map(Self) + } +} + +/// This allows (de)serializing to/from non-self-describing formats by avoiding serializing `Variant`s +// Can't just use a HashMap because VariantDispatch doesn't implement Hash, and this avoids cloning all of the entries anyway +struct DictionaryDispatch(Dictionary); + +#[derive(Serialize, Deserialize)] +struct DictionaryDispatchEntry { + key: VariantDispatch, + value: VariantDispatch, +} + +impl<'d> Serialize for DictionaryDispatch { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + let mut ser = serializer.serialize_seq(Some(self.0.len() as usize))?; + for (key, value) in self.0.iter() { + ser.serialize_element(&DictionaryDispatchEntry { + key: key.dispatch(), + value: value.dispatch(), + })?; + } + ser.end() + } +} + +impl<'de> Deserialize<'de> for DictionaryDispatch { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct DictionaryDispatchVisitor; + impl<'de> Visitor<'de> for DictionaryDispatchVisitor { + type Value = DictionaryDispatch; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a sequence of VariantDispatch pairs") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let dict = Dictionary::new(); + while let Some(DictionaryDispatchEntry { key, value }) = seq.next_element()? { + dict.insert(Variant::from(&key), Variant::from(&value)) + } + Ok(DictionaryDispatch(dict.into_shared())) + } + } + deserializer.deserialize_seq(DictionaryDispatchVisitor) + } +} + +impl Serialize for VariantDispatch { + #[inline] + fn serialize(&self, ser: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + use VariantDispatch::*; + + const NAME: &str = "VariantDispatch"; + + macro_rules! newtype_variant { + ($t:expr, $v:expr) => { + ser.serialize_newtype_variant(NAME, $t as u32, $t.name(), $v) + }; + } + match self { + Nil => { + ser.serialize_unit_variant(NAME, VariantType::Nil as u32, VariantType::Nil.name()) + } + Bool(v) => newtype_variant!(VariantType::Bool, v), + I64(v) => newtype_variant!(VariantType::I64, v), + F64(v) => newtype_variant!(VariantType::F64, v), + GodotString(v) => newtype_variant!(VariantType::GodotString, v), + Vector2(v) => newtype_variant!(VariantType::Vector2, v), + Rect2(v) => newtype_variant!(VariantType::Rect2, v), + Vector3(v) => newtype_variant!(VariantType::Vector3, v), + Transform2D(v) => newtype_variant!(VariantType::Transform2D, v), + Plane(v) => newtype_variant!(VariantType::Plane, v), + Quat(v) => newtype_variant!(VariantType::Quat, v), + Aabb(v) => newtype_variant!(VariantType::Aabb, v), + Basis(v) => newtype_variant!(VariantType::Basis, v), + Transform(v) => newtype_variant!(VariantType::Transform, v), + Color(v) => newtype_variant!(VariantType::Color, v), + NodePath(v) => newtype_variant!(VariantType::NodePath, v), + Rid(_) => Err(S::Error::custom("Serialization of RID's is not supported")), + Object(_) => Err(S::Error::custom( + "Serialization of Objects is not supported", + )), + Dictionary(v) => { + newtype_variant!(VariantType::Dictionary, &DictionaryDispatch(v.new_ref())) + } + VariantArray(v) => { + // Allows serializing to non-self-describing formats by avoiding serializing `Variant`s + let vec = v.iter().map(|v| v.dispatch()).collect::>(); + newtype_variant!(VariantType::VariantArray, &vec) + } + ByteArray(v) => newtype_variant!(VariantType::ByteArray, v), + Int32Array(v) => newtype_variant!(VariantType::Int32Array, v), + Float32Array(v) => newtype_variant!(VariantType::Float32Array, v), + StringArray(v) => newtype_variant!(VariantType::StringArray, v), + Vector2Array(v) => newtype_variant!(VariantType::Vector2Array, v), + Vector3Array(v) => newtype_variant!(VariantType::Vector3Array, v), + ColorArray(v) => newtype_variant!(VariantType::ColorArray, v), + } + } +} + +struct VariantDispatchVisitor; + +impl<'de> Visitor<'de> for VariantDispatchVisitor { + type Value = VariantDispatch; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("enum VariantDispatch") + } + + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + use VariantType::*; + let (t, v) = data.variant::()?; + Ok(match t.0 { + Nil => { + v.unit_variant()?; + VariantDispatch::Nil + } + Bool => VariantDispatch::Bool(v.newtype_variant()?), + I64 => VariantDispatch::I64(v.newtype_variant()?), + F64 => VariantDispatch::F64(v.newtype_variant()?), + GodotString => VariantDispatch::GodotString(v.newtype_variant()?), + Vector2 => VariantDispatch::Vector2(v.newtype_variant()?), + Rect2 => VariantDispatch::Rect2(v.newtype_variant()?), + Vector3 => VariantDispatch::Vector3(v.newtype_variant()?), + Transform2D => VariantDispatch::Transform2D(v.newtype_variant()?), + Plane => VariantDispatch::Plane(v.newtype_variant()?), + Quat => VariantDispatch::Quat(v.newtype_variant()?), + Aabb => VariantDispatch::Aabb(v.newtype_variant()?), + Basis => VariantDispatch::Basis(v.newtype_variant()?), + Transform => VariantDispatch::Transform(v.newtype_variant()?), + Color => VariantDispatch::Color(v.newtype_variant()?), + NodePath => VariantDispatch::NodePath(v.newtype_variant()?), + Rid => return Err(A::Error::custom("Not sure how an RID got serialized")), + Object => return Err(A::Error::custom("Not sure how an Object got serialized")), + Dictionary => VariantDispatch::Dictionary(v.newtype_variant::()?.0), + VariantArray => VariantDispatch::VariantArray( + v.newtype_variant::>()? + .iter() + .map(Into::::into) + .collect::>() + .into_shared(), + ), + ByteArray => VariantDispatch::ByteArray(v.newtype_variant()?), + Int32Array => VariantDispatch::Int32Array(v.newtype_variant()?), + Float32Array => VariantDispatch::Float32Array(v.newtype_variant()?), + StringArray => VariantDispatch::StringArray(v.newtype_variant()?), + Vector2Array => VariantDispatch::Vector2Array(v.newtype_variant()?), + Vector3Array => VariantDispatch::Vector3Array(v.newtype_variant()?), + ColorArray => VariantDispatch::ColorArray(v.newtype_variant()?), + }) + } +} + +impl<'de> Deserialize<'de> for VariantDispatch { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_enum( + "VariantDispatch", + VariantType::NAMES, + VariantDispatchVisitor, + ) + } +} diff --git a/gdnative-core/src/core_types/vector2.rs b/gdnative-core/src/core_types/vector2.rs index b3fc7005d..dfed5886f 100644 --- a/gdnative-core/src/core_types/vector2.rs +++ b/gdnative-core/src/core_types/vector2.rs @@ -3,6 +3,7 @@ use glam::Vec2; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; #[derive(Copy, Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Vector2 { pub x: f32, diff --git a/gdnative-core/src/core_types/vector3.rs b/gdnative-core/src/core_types/vector3.rs index 655f2a06a..69b6f3e78 100644 --- a/gdnative-core/src/core_types/vector3.rs +++ b/gdnative-core/src/core_types/vector3.rs @@ -4,6 +4,7 @@ use glam::Vec3A; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; #[derive(Copy, Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub struct Vector3 { pub x: f32, diff --git a/gdnative/Cargo.toml b/gdnative/Cargo.toml index f01ff9fe6..2473e53a1 100644 --- a/gdnative/Cargo.toml +++ b/gdnative/Cargo.toml @@ -14,6 +14,7 @@ edition = "2018" [features] default = ["bindings"] formatted = ["gdnative-bindings/formatted", "gdnative-bindings/one_class_one_file"] +serde = ["gdnative-core/serde"] gd_test = ["gdnative-core/gd_test"] type_tag_fallback = ["gdnative-core/type_tag_fallback"] diff --git a/test/Cargo.toml b/test/Cargo.toml index 6ea107966..46e5aa9da 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -13,6 +13,13 @@ default = [] type_tag_fallback = ["gdnative/type_tag_fallback"] [dependencies] -gdnative = { path = "../gdnative", features = ["gd_test"] } +gdnative = { path = "../gdnative", features = ["gd_test", "serde"] } gdnative-derive = { path = "../gdnative-derive" } approx = "0.5.0" +ron = "0.6.4" +serde = "1" +serde_json = "1.0.64" +bincode = "1.3.3" +serde_cbor = "0.11.1" +serde_yaml = "0.8.17" +rmp-serde = "0.15.5" diff --git a/test/src/lib.rs b/test/src/lib.rs index eb6931a05..af69adb19 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -8,6 +8,7 @@ mod test_free_ub; mod test_map_owned; mod test_register; mod test_return_leak; +mod test_serde; mod test_vararray_return; mod test_variant_call_args; mod test_variant_ops; @@ -66,6 +67,7 @@ pub extern "C" fn run_tests( status &= test_map_owned::run_tests(); status &= test_register::run_tests(); status &= test_return_leak::run_tests(); + status &= test_serde::run_tests(); status &= test_variant_call_args::run_tests(); status &= test_variant_ops::run_tests(); status &= test_vararray_return::run_tests(); diff --git a/test/src/test_serde.rs b/test/src/test_serde.rs new file mode 100644 index 000000000..fe2a3ca48 --- /dev/null +++ b/test/src/test_serde.rs @@ -0,0 +1,314 @@ +use gdnative::prelude::*; +use serde::{Deserialize, Serialize}; + +pub(crate) fn run_tests() -> bool { + println!(" -- serde tests:"); + let mut status = true; + + // All tests depend on these invariants + status &= test_variant_eq(); + status &= test_dispatch_eq(); + if !status { + gdnative::godot_error!(" !!!! Can't run serde tests, Foo::[to/from]_variant is broken!"); + return false; + } + + status &= test_ron(); + status &= test_json(); + status &= test_yaml(); + status &= test_cbor(); + status &= test_msgpack(); + status &= test_bincode(); + + status +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, ToVariant, FromVariant)] +struct Foo { + some: Option, + none: Option, + b: bool, + int: i64, + float: f64, + str: GodotString, + vec2: Vector2, + rect2: Rect2, + vec3: Vector3, + xform_2d: Transform2D, + plane: Plane, + quat: Quat, + aabb: Aabb, + basis: Basis, + xform: Transform, + color: Color, + path: NodePath, + // dict: Dictionary, //TODO: PartialEq + // v_arr: VariantArray, //TODO: PartialEq + byte_arr: ByteArray, + int_arr: Int32Array, + float_arr: Float32Array, + str_arr: StringArray, + vec2_arr: Vector2Array, + vec3_arr: Vector3Array, + color_arr: ColorArray, +} + +impl Foo { + fn new() -> Self { + Self { + some: Some(true), + none: None, + b: false, + int: 1, + float: 2.0, + str: "this is a GodotString".into(), + vec2: Vector2::RIGHT, + rect2: Rect2 { + position: Vector2 { x: 46.47, y: -2.0 }, + size: Vector2 { x: 3.0, y: 4.8 }, + }, + vec3: Vector3::BACK, + xform_2d: Transform2D { + x: Vector2::RIGHT, + y: Vector2::DOWN, + origin: Vector2::ZERO, + }, + plane: Plane { + normal: Vector3::ONE.normalized(), + d: 3.0, + }, + quat: Quat::new(4.1, 5.2, 6.3, 7.5), + aabb: Aabb { + position: Vector3::new(8.2, 9.8, 10.11), + size: Vector3::new(12.13, 14.15, 16.17), + }, + basis: Basis::identity().rotated(Vector3::UP, std::f32::consts::TAU / 3.0), + xform: Transform { + basis: Basis::from_euler(Vector3::new(18.19, -20.21, 22.23)), + origin: Vector3::new(24.25, 26.27, 28.29), + }, + color: Color::from_rgb(0.549, 0.0, 1.0), + path: "/root/Node".into(), + byte_arr: ByteArray::from_slice(&[30u8, 31u8, 32u8]), + int_arr: Int32Array::from_slice(&[33i32, 34i32, 35i32, 36i32]), + float_arr: Float32Array::from_slice(&[37.38, 39.40]), + str_arr: StringArray::from_vec(vec!["hello".into(), "world".into()]), + vec2_arr: Vector2Array::from_slice(&[ + Vector2::UP, + Vector2::UP, + Vector2::DOWN, + Vector2::DOWN, + Vector2::LEFT, + Vector2::RIGHT, + Vector2::LEFT, + Vector2::RIGHT, + ]), + vec3_arr: Vector3Array::from_slice(&[ + Vector3::ONE * 41.0, + Vector3::BACK * 42.43, + Vector3::FORWARD * 44.45, + ]), + color_arr: ColorArray::from_slice(&[Color::from_rgba(0.0, 1.0, 0.627, 0.8)]), + } + } +} + +/// Sanity check that a round trip through Variant preserves equality for Foo. +fn test_variant_eq() -> bool { + println!(" -- test_variant_eq"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + let variant = foo.to_variant(); + let result = Foo::from_variant(&variant).expect("Foo::from_variant"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_variant_eq failed"); + } + + ok +} + +/// Sanity check that a round trip through VariantDispatch preserves equality for Foo. +fn test_dispatch_eq() -> bool { + println!(" -- test_dispatch_eq"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + let dispatch = foo.to_variant().dispatch(); + let result = Foo::from_variant(&Variant::from(&dispatch)).expect("Foo from Dispatch"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_dispatch_eq failed"); + } + + ok +} + +fn test_ron() -> bool { + println!(" -- test_ron"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let ron_str = ron::to_string(&foo).expect("Foo to RON str"); + let mut de = ron::Deserializer::from_str(ron_str.as_ref()); + let result = Foo::deserialize(de.as_mut().expect("deserialize Foo from RON")).unwrap(); + assert_eq!(foo, result); + + let ron_disp_str = ron::to_string(&foo.to_variant().dispatch()).expect("Dispatch to RON"); + let mut de = ron::Deserializer::from_str(ron_disp_str.as_ref()); + let de = de + .as_mut() + .expect("disp_round_trip ron::Deserializer::from_str"); + let disp = VariantDispatch::deserialize(de).expect("Dispatch from RON"); + let result = Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from RON"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_ron failed"); + } + + ok +} + +fn test_json() -> bool { + println!(" -- test_json"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let json_str = serde_json::to_string(&foo).expect("Foo to JSON"); + let result = serde_json::from_str::(json_str.as_ref()).expect("Foo from JSON"); + assert_eq!(foo, result); + + let foo = Foo::new(); + let json_disp_str = + serde_json::to_string(&foo.to_variant().dispatch()).expect("Foo Dispatch to JSON"); + let disp = serde_json::from_str::(json_disp_str.as_ref()) + .expect("Dispatch from JSON"); + let result = Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from JSON"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_json failed"); + } + + ok +} + +fn test_yaml() -> bool { + println!(" -- test_yaml"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let yaml_str = serde_yaml::to_string(&foo).expect("Foo to YAML"); + let result = serde_yaml::from_str::(&yaml_str).expect("Foo from YAML"); + assert_eq!(foo, result); + + let yaml_str = + serde_yaml::to_string(&foo.to_variant().dispatch()).expect("Dispatch to YAML"); + let disp = serde_yaml::from_str::(&yaml_str).expect("Dispatch from YAML"); + let result = Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from YAML"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_yaml failed"); + } + + ok +} + +fn test_cbor() -> bool { + println!(" -- test_cbor"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let cbor_bytes = serde_cbor::to_vec(&foo).expect("Foo to CBOR"); + let result = serde_cbor::from_slice::(&cbor_bytes).expect("Foo from CBOR"); + assert_eq!(foo, result); + + let cbor_bytes = + serde_cbor::to_vec(&foo.to_variant().dispatch()).expect("Dispatch to CBOR"); + let disp = + serde_cbor::from_slice::(&cbor_bytes).expect("Dispatch from CBOR"); + let result = Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from CBOR"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_cbor failed"); + } + + ok +} + +fn test_msgpack() -> bool { + println!(" -- test_msgpack"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let msgpack_bytes = rmp_serde::to_vec_named(&foo).expect("Foo to MessagePack"); + let result = + rmp_serde::from_read_ref::<_, Foo>(&msgpack_bytes).expect("Foo from MessagePack"); + assert_eq!(foo, result); + + let msgpack_disp_bytes = + rmp_serde::to_vec_named(&foo.to_variant().dispatch()).expect("Dispatch to MessagePack"); + let disp = rmp_serde::from_read_ref::<_, VariantDispatch>(&msgpack_disp_bytes) + .expect("Dispatch from MessagePack"); + let result = + Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from MessagePack"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_msgpack failed"); + } + + ok +} + +fn test_bincode() -> bool { + println!(" -- test_bincode"); + + let ok = std::panic::catch_unwind(|| { + let foo = Foo::new(); + + let bincode_bytes = bincode::serialize(&foo).expect("Foo to bincode"); + let result = bincode::deserialize::(bincode_bytes.as_ref()).expect("Foo from bincode"); + assert_eq!(foo, result); + + let bincode_bytes = + bincode::serialize(&foo.to_variant().dispatch()).expect("Dispatch to bincode"); + let disp = bincode::deserialize::(bincode_bytes.as_ref()) + .expect("Dispatch from bincode"); + let result = + Foo::from_variant(&Variant::from(&disp)).expect("Foo from Dispatch from bincode"); + assert_eq!(foo, result); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_bincode failed"); + } + + ok +}