From 15757f3169d3b25d6bc9ddf2111838ddb06a0cf8 Mon Sep 17 00:00:00 2001 From: Waridley Date: Mon, 14 Jun 2021 18:01:29 -0500 Subject: [PATCH] WIP serde support Some tests working, still a few TODO's left --- gdnative-core/Cargo.toml | 3 +- gdnative-core/src/core_types/color.rs | 1 + gdnative-core/src/core_types/dictionary.rs | 58 ++ gdnative-core/src/core_types/error.rs | 1 + gdnative-core/src/core_types/geom/aabb.rs | 1 + gdnative-core/src/core_types/geom/basis.rs | 1 + gdnative-core/src/core_types/geom/plane.rs | 1 + .../src/core_types/geom/transform.rs | 1 + gdnative-core/src/core_types/mod.rs | 2 +- gdnative-core/src/core_types/node_path.rs | 57 ++ gdnative-core/src/core_types/quat.rs | 1 + gdnative-core/src/core_types/rect2.rs | 1 + gdnative-core/src/core_types/rid.rs | 46 ++ gdnative-core/src/core_types/string.rs | 49 ++ gdnative-core/src/core_types/transform2d.rs | 1 + gdnative-core/src/core_types/typed_array.rs | 60 ++ gdnative-core/src/core_types/variant.rs | 30 + gdnative-core/src/core_types/variant/serde.rs | 746 ++++++++++++++++++ gdnative-core/src/core_types/variant_array.rs | 61 ++ gdnative-core/src/core_types/vector2.rs | 1 + gdnative-core/src/core_types/vector3.rs | 1 + gdnative/Cargo.toml | 1 + test/Cargo.toml | 4 + test/src/lib.rs | 2 + test/src/test_serde.rs | 203 +++++ 25 files changed, 1331 insertions(+), 2 deletions(-) create mode 100644 gdnative-core/src/core_types/variant/serde.rs create mode 100644 test/src/test_serde.rs diff --git a/gdnative-core/Cargo.toml b/gdnative-core/Cargo.toml index 702ec9df3..4cc7784cf 100644 --- a/gdnative-core/Cargo.toml +++ b/gdnative-core/Cargo.toml @@ -12,7 +12,7 @@ edition = "2018" [features] default = ["nativescript"] -gd_test = [] +gd_test = ["serde"] nativescript = ["bitflags", "parking_lot"] type_tag_fallback = [] @@ -24,6 +24,7 @@ glam = "0.16.0" indexmap = "1.6.0" ahash = "0.7.0" once_cell = "1.7.2" +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/dictionary.rs b/gdnative-core/src/core_types/dictionary.rs index a4183c08a..ed5e05868 100644 --- a/gdnative-core/src/core_types/dictionary.rs +++ b/gdnative-core/src/core_types/dictionary.rs @@ -573,6 +573,64 @@ where } } +#[cfg(feature = "serde")] +pub(super) mod serde { + use super::*; + use ::serde::{ + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + + impl Serialize for Dictionary { + #[inline] + fn serialize(&self, ser: S) -> Result + where + S: Serializer, + { + let mut ser = ser.serialize_map(Some(self.len() as usize))?; + for (key, value) in self.iter() { + ser.serialize_entry(&key, &value)? + } + ser.end() + } + } + + pub(in super::super) struct DictionaryVisitor; + + impl<'de> Visitor<'de> for DictionaryVisitor { + type Value = Dictionary; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a Dictionary") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let dict = Dictionary::new(); + while let Some((key, value)) = map.next_entry::()? { + dict.insert(key, value) + } + Ok(dict) + } + } + + impl<'de, Access: ThreadAccess> Deserialize<'de> for Dictionary { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer + .deserialize_map(DictionaryVisitor) + .map(|dict| unsafe { dict.cast_access() }) + } + } +} + godot_test!(test_dictionary { use std::collections::HashSet; 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 73507f2a5..56b2ecc39 100644 --- a/gdnative-core/src/core_types/geom/basis.rs +++ b/gdnative-core/src/core_types/geom/basis.rs @@ -4,6 +4,7 @@ use core::ops::Mul; /// 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 260744a76..9c754a051 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 serde { + 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 c4e36ffb9..081cd5141 100644 --- a/gdnative-core/src/core_types/quat.rs +++ b/gdnative-core/src/core_types/quat.rs @@ -1,6 +1,7 @@ use super::IsEqualApprox; #[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..b72fd5c07 100644 --- a/gdnative-core/src/core_types/rect2.rs +++ b/gdnative-core/src/core_types/rect2.rs @@ -1,5 +1,6 @@ use super::Vector2; +#[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/rid.rs b/gdnative-core/src/core_types/rid.rs index 02cf6ceaf..cc2fe280b 100644 --- a/gdnative-core/src/core_types/rid.rs +++ b/gdnative-core/src/core_types/rid.rs @@ -84,3 +84,49 @@ impl PartialOrd for Rid { } } } + +#[cfg(feature = "serde")] +mod serde { + use super::*; + use ::serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + + impl Serialize for Rid { + #[inline] + fn serialize(&self, serializer: S) -> Result<::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_unit() + } + } + + impl<'de> Deserialize<'de> for Rid { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct RidVisitor; + impl<'de> Visitor<'de> for RidVisitor { + type Value = Rid; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("Unit as an RID placeholder") + } + + fn visit_unit(self) -> Result + where + E: Error, + { + Ok(Rid::new()) + } + } + deserializer.deserialize_unit(RidVisitor)?; + Ok(Rid::new()) + } + } +} diff --git a/gdnative-core/src/core_types/string.rs b/gdnative-core/src/core_types/string.rs index a44ac8333..222a4d592 100644 --- a/gdnative-core/src/core_types/string.rs +++ b/gdnative-core/src/core_types/string.rs @@ -612,6 +612,55 @@ where } } +#[cfg(feature = "serde")] +mod serde { + 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> serde::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..bace0db5e 100644 --- a/gdnative-core/src/core_types/transform2d.rs +++ b/gdnative-core/src/core_types/transform2d.rs @@ -1,5 +1,6 @@ use super::Vector2; +#[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..0460c7c9e 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 serde { + 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..1fe3ea45f 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 serde; + // TODO: implement Debug, PartialEq, etc. /// A `Variant` can represent many of godot's core types. @@ -103,12 +106,26 @@ macro_rules! decl_variant_type { ) => { #[repr(u32)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] pub enum VariantType { $( $variant = $c_const as u32, )* } + impl VariantType { + pub const NAMES: &'static [&'static str] = &[ + $(stringify!($variant),)* + ]; + + #[inline] + pub fn name(&self) -> &'static str { + match *self { + $(Self::$variant => 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 +149,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() + } + } + } } } diff --git a/gdnative-core/src/core_types/variant/serde.rs b/gdnative-core/src/core_types/variant/serde.rs new file mode 100644 index 000000000..2aee70a15 --- /dev/null +++ b/gdnative-core/src/core_types/variant/serde.rs @@ -0,0 +1,746 @@ +use super::super::{ + dictionary::serde::DictionaryVisitor, variant_array::serde::VariantArrayVisitor, +}; +use super::*; +use ::serde::{ + de::{EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; +use once_cell::sync::Lazy; +use std::fmt::Formatter; + +impl Serialize for Variant { + #[inline] + fn serialize(&self, ser: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + match self.dispatch() { + VariantDispatch::Nil => ser.serialize_none(), + VariantDispatch::Bool(v) => ser.serialize_bool(v), + VariantDispatch::I64(v) => ser.serialize_i64(v), + VariantDispatch::F64(v) => ser.serialize_f64(v), + VariantDispatch::GodotString(v) => ser.serialize_str(&v.to_string()), + VariantDispatch::Vector2(v) => v.serialize(ser), + VariantDispatch::Rect2(v) => v.serialize(ser), + VariantDispatch::Vector3(v) => v.serialize(ser), + VariantDispatch::Transform2D(v) => v.serialize(ser), + VariantDispatch::Plane(v) => v.serialize(ser), + VariantDispatch::Quat(v) => v.serialize(ser), + VariantDispatch::Aabb(v) => v.serialize(ser), + VariantDispatch::Basis(v) => v.serialize(ser), + VariantDispatch::Transform(v) => v.serialize(ser), + VariantDispatch::Color(v) => v.serialize(ser), + VariantDispatch::NodePath(v) => v.serialize(ser), + VariantDispatch::Rid(_rid) => ser.serialize_newtype_variant( + "Variant", + VariantType::Rid as u32, + VariantType::Rid.name(), + &(), + ), + VariantDispatch::Object(_object) => ser.serialize_newtype_variant( + "Variant", + VariantType::Object as u32, + VariantType::Object.name(), + &(), + ), + VariantDispatch::Dictionary(v) => v.serialize(ser), + VariantDispatch::VariantArray(v) => v.serialize(ser), + VariantDispatch::ByteArray(v) => v.serialize(ser), + VariantDispatch::Int32Array(v) => v.serialize(ser), + VariantDispatch::Float32Array(v) => v.serialize(ser), + VariantDispatch::StringArray(v) => v.serialize(ser), + VariantDispatch::Vector2Array(v) => v.serialize(ser), + VariantDispatch::Vector3Array(v) => v.serialize(ser), + VariantDispatch::ColorArray(v) => v.serialize(ser), + } + } +} + +/// 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); + +impl<'d> Serialize for DictionaryDispatch { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + let mut ser = serializer.serialize_map(Some(self.0.len() as usize))?; + for (key, value) in self.0.iter() { + ser.serialize_entry(&key.dispatch(), &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 map of VariantDispatch to VariantDispatch") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let dict = Dictionary::new(); + while let Some((key, value)) = + map.next_entry::()? + { + dict.insert(Variant::from(&key), Variant::from(&value)) + } + Ok(DictionaryDispatch(dict.into_shared())) + } + } + deserializer.deserialize_map(DictionaryDispatchVisitor) + } +} + +impl Serialize for VariantDispatch { + #[inline] + fn serialize(&self, ser: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { + use VariantDispatch::*; + macro_rules! newtype_variant { + ($t:expr, $v:expr) => { + ser.serialize_newtype_variant("VariantDispatch", $t as u32, $t.name(), $v) + }; + } + match self { + Nil => ser.serialize_unit_variant( + "VariantDispatch", + 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(v) => newtype_variant!(VariantType::Rid, v), + Object(_) => newtype_variant!(VariantType::Object, &Variant::new()), + 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 { + 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 => VariantDispatch::Rid(v.newtype_variant()?), + Object => { + // should return None + VariantDispatch::Object(v.newtype_variant::>()?.to_variant()) + } + 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, + ) + } +} + +struct VariantVisitor; + +impl<'de> Visitor<'de> for VariantVisitor { + type Value = Variant; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a Variant") + } + + fn visit_bool(self, v: bool) -> Result + where + E: Error, + { + Ok(v.to_variant()) + } + + fn visit_i64(self, v: i64) -> Result + where + E: Error, + { + Ok(v.to_variant()) + } + + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_f64(self, v: f64) -> Result + where + E: Error, + { + Ok(v.to_variant()) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + if v == "Nil" { + //`VariantDispatch::Nil` could be represented as the string "Nil" + return Ok(Variant::new()); + } + Ok(v.to_variant()) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + Ok(ByteArray::from_slice(v).to_variant()) + } + + fn visit_none(self) -> Result + where + E: Error, + { + Ok(Variant::new()) + } + + fn visit_some(self, deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_unit(self) -> Result + where + E: Error, + { + Ok(().to_variant()) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(VariantVisitor) + } + + fn visit_seq(self, seq: A) -> Result>::Error> + where + A: SeqAccess<'de>, + { + let arr = VariantArrayVisitor.visit_seq(seq)?; + let len = arr.len(); + + if len == 1 { + if let VariantDispatch::VariantArray(arr) = arr.get(0).dispatch() { + if arr.len() == 3 { + if let Some(v) = arr.get(0).try_to_vector3() { + //assume format may have treated Basis as a sequence of one element + if let Some(basis) = basis_seq(&arr, v) { + return Ok(basis.to_variant()); + } + } + } + } + } else if len == 2 { + let first = arr.get(0).dispatch(); + match first { + VariantDispatch::F64(x) => { + let x = x as f32; + if let Some(y) = f32_field(&arr.get(1)) { + return Ok(Vector2 { x, y }.to_variant()); + } + } + VariantDispatch::Vector2(position) => { + if let Some(size) = arr.get(1).try_to_vector2() { + return Ok(Rect2 { position, size }.to_variant()); + } + } + VariantDispatch::Vector3(pos_or_norm) => { + let next = arr.get(1); + if let Some(d) = f32_field(&next) { + let normal = pos_or_norm; + return Ok(Plane { normal, d }.to_variant()); + } else if let Some(size) = next.try_to_vector3() { + let position = pos_or_norm; + return Ok(Aabb { position, size }.to_variant()); + } + } + _ => {} + } + } else if len == 3 { + let first = arr.get(0).dispatch(); + match first { + VariantDispatch::F64(x) => { + let x = x as f32; + if let Some(y) = f32_field(&arr.get(1)) { + if let Some(z) = f32_field(&arr.get(2)) { + return Ok(Vector3 { x, y, z }.to_variant()); + } + } + } + VariantDispatch::Vector2(x) => { + if let Some(y) = arr.get(1).try_to_vector2() { + if let Some(origin) = arr.get(2).try_to_vector2() { + return Ok(Transform2D { x, y, origin }.to_variant()); + } + } + } + VariantDispatch::Vector3(v) => { + if let Some(basis) = basis_seq(&arr, v) { + return Ok(basis.to_variant()); + } + } + _ => {} + } + } else if len == 4 { + if let Some(r) = f32_field(&arr.get(0)) { + if let Some(g) = f32_field(&arr.get(1)) { + if let Some(b) = f32_field(&arr.get(2)) { + if let Some(a) = f32_field(&arr.get(3)) { + //Assume it's a Color rather than a Quat since Godot calls arrays of + //4-float structs `ColorArray`s. + return Ok(Color { r, g, b, a }.to_variant()); + } + } + } + } + } + + Ok(arr.owned_to_variant()) + } + + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'de>, + { + let dict = DictionaryVisitor.visit_map(map)?; + let len = dict.len(); + if len == 1 { + let (key, value) = dict.iter().next().unwrap(); + if let Some(key) = key.try_to_string() { + if let Some(v) = string_tagged(&key, value) { + return Ok(v); + } + } else if let Some(key) = key.try_to_i64() { + if let Some(v) = int_tagged(key, value) { + return Ok(v); + } + } + } else if len == 2 { + if let Some(v) = vec2_plane_xform_rect2_or_aabb(&dict) { + return Ok(v); + } + } else if len == 3 { + if let Some(v) = vec3_or_xform2d(&dict) { + return Ok(v); + } + } else if len == 4 { + if let Some(v) = quat_or_color(&dict) { + return Ok(v); + } + } + + //Didn't appear to be any core type, just return the dictionary. + Ok(dict.owned_to_variant()) + } +} + +impl<'de> Deserialize<'de> for Variant { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(VariantVisitor) + } +} + +fn basis_seq(arr: &VariantArray, first: Vector3) -> Option { + if let Some(second) = arr.get(1).try_to_vector3() { + if let Some(third) = arr.get(2).try_to_vector3() { + return Some(Basis { + elements: [first, second, third], + }); + } + } + None +} + +fn string_tagged(key: &str, value: Variant) -> Option { + let s = key; + let value_type = value.get_type(); + if (s == value_type.name()) + || ((value_type == Option::<()>::None.to_variant().get_type()) && (s == "Object")) + || ((value_type == ().to_variant().get_type()) && (s == "Rid")) + //maybe a Basis represented as a Map, in which case visit_seq will have assumed [Vector3; 3] was a Basis + || ((value_type == VariantType::Basis) && (s == "elements")) + { + return Some(value); + } else if s == VariantType::NodePath.name() { + if let Some(path) = value.try_to_string() { + return Some(NodePath::from_str(&*path).to_variant()); + } + } else if let Some(arr) = value.try_to_array() { + if let Some(s) = s.strip_suffix("Array") { + match s { + "Variant" => return Some(value), //for completeness, should have been handled by `s == value_type.name()` + "Byte" => return Some(ByteArray::from_variant_array(&arr).to_variant()), + "Int32" => return Some(Int32Array::from_variant_array(&arr).to_variant()), + "Float32" => return Some(Float32Array::from_variant_array(&arr).to_variant()), + "Vector2" => return Some(Vector2Array::from_variant_array(&arr).to_variant()), + "Vector3" => return Some(Vector3Array::from_variant_array(&arr).to_variant()), + "Color" => return Some(ColorArray::from_variant_array(&arr).to_variant()), + _ => {} + } + } + } + None +} + +fn int_tagged(key: i64, value: Variant) -> Option { + //TODO: The field enums serde generates for structs could get stored as ints. + // We could either hand-write all the impls so we know what the int will be, + // or assume serde will keep the indices the same as the declaration order, + // or just not support deserializing Variants from formats that store the field + // identifier as an int (VariantDispatch should still work). + let i = key; + if i == value.get_type() as i64 { + return Some(value); + } else if (i == VariantType::Object as i64) && (value.get_type() == VariantType::Nil) { + return Some(Variant::new()); + } else if (i == VariantType::Rid as i64) && (value.get_type() == ().to_variant().get_type()) { + return Some(Rid::new().to_variant()); + } else if let Some(arr) = value.try_to_array() { + if i == VariantType::ByteArray as i64 { + return Some(ByteArray::from_variant_array(&arr).to_variant()); + } else if i == VariantType::Int32Array as i64 { + return Some(Int32Array::from_variant_array(&arr).to_variant()); + } else if i == VariantType::Float32Array as i64 { + return Some(Float32Array::from_variant_array(&arr).to_variant()); + } else if i == VariantType::Vector2Array as i64 { + return Some(Vector2Array::from_variant_array(&arr).to_variant()); + } else if i == VariantType::Vector3Array as i64 { + return Some(Vector3Array::from_variant_array(&arr).to_variant()); + } else if i == VariantType::ColorArray as i64 { + return Some(ColorArray::from_variant_array(&arr).to_variant()); + } + } + None +} + +/// Struct to store possible core type field names as static Variants for optimized dictionary deserialization. +struct Keys { + /// Vector2, Vector3, Transform2D, Quat + pub x: Variant, + /// Vector2, Vector3, Transform2D, Quat + pub y: Variant, + /// Vector3, Quat + pub z: Variant, + /// Quat + pub w: Variant, + /// Color + pub r: Variant, + /// Color + pub g: Variant, + /// Color + pub b: Variant, + /// Color + pub a: Variant, + /// Plane + pub normal: Variant, + /// Plane + pub d: Variant, + /// Transform + pub basis: Variant, + /// Transform, Transform2D + pub origin: Variant, + /// Rect2, Aabb + pub position: Variant, + /// Rect2, Aabb + pub size: Variant, +} + +static KEYS: Lazy = Lazy::new(|| Keys { + x: "x".to_variant(), + y: "y".to_variant(), + z: "z".to_variant(), + w: "w".to_variant(), + r: "r".to_variant(), + g: "g".to_variant(), + b: "b".to_variant(), + a: "a".to_variant(), + normal: "normal".to_variant(), + d: "d".to_variant(), + basis: "basis".to_variant(), + origin: "origin".to_variant(), + position: "position".to_variant(), + size: "size".to_variant(), +}); + +/// ["x", "y"] +/// +/// Used for Vector2, as well as short-circuiting Vector3 and Transform2D. +static XY_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.x, &keys.y] + .iter() + .collect::>() + .into_shared() +}); + +/// Plane field names. +static PLANE_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.normal, &keys.d] + .iter() + .collect::>() + .into_shared() +}); + +/// Transform (3D) field names. +static XFORM_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.basis, &keys.origin] + .iter() + .collect::>() + .into_shared() +}); + +/// Bounding box field names, for Rect2 and Aabb. +static BB_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.position, &keys.size] + .iter() + .collect::>() + .into_shared() +}); + +/// `dict` has been verified to contain 2 entries. +/// +/// This function checks the keys to determine which Variant type to return, and verifies the types +/// of the values before copying them into the correct core type and returning it as a Variant. +fn vec2_plane_xform_rect2_or_aabb(dict: &Dictionary) -> Option { + let keys = &*KEYS; + unsafe { + //SAFETY: `dict` is Unique, so it shouldn't be modified through another reference, + // we verify that all keys exist before calling `get_ref`, so the dictionary won't re-allocate, + // and we won't be returning any references, so they can't be invalidated later. + if dict.contains_all(&*XY_KEYS) { + get_f32(dict, &keys.x) + .zip(get_f32(dict, &keys.y)) + .map(|(x, y)| Vector2 { x, y }.to_variant()) + } else if dict.contains_all(&*PLANE_KEYS) { + dict.get_ref(&keys.normal) + .try_to_vector3() + .zip(get_f32(dict, &keys.d)) + .map(|(normal, d)| Plane { normal, d }.to_variant()) + } else if dict.contains_all(&*XFORM_KEYS) { + dict.get_ref(&keys.basis) + .try_to_basis() + .zip(dict.get_ref(&keys.origin).try_to_vector3()) + .map(|(basis, origin)| Transform { basis, origin }.to_variant()) + } else if dict.contains_all(&*BB_KEYS) { + match dict.get_ref(&keys.position).dispatch() { + VariantDispatch::Vector2(position) => dict + .get_ref(&keys.size) + .try_to_vector2() + .map(|size| Rect2 { position, size }.to_variant()), + VariantDispatch::Vector3(position) => dict + .get_ref(&keys.size) + .try_to_vector3() + .map(|size| Aabb { position, size }.to_variant()), + _ => None, + } + } else { + None + } + } +} + +/// `dict` has been verified to contain 3 entries. +/// +/// This function checks the keys to determine which Variant type to return, and verifies the types +/// of the values before copying them into the correct core type and converting it to a Variant. +fn vec3_or_xform2d(dict: &Dictionary) -> Option { + if dict.contains_all(&*XY_KEYS) { + let keys = &*KEYS; + unsafe { + //SAFETY: `dict` is Unique, so it shouldn't be modified through another reference, + // we verify that all keys exist before calling `get_ref`, so the dictionary won't re-allocate, + // and we won't be returning any references, so they can't be invalidated later. + if dict.contains(&keys.z) { + get_f32(dict, &keys.x) + .zip(get_f32(dict, &keys.y)) + .zip(get_f32(dict, &keys.z)) + .map(|((x, y), z)| Vector3 { x, y, z }.to_variant()) + } else if dict.contains(&keys.origin) { + dict.get_ref(&keys.x) + .try_to_vector2() + .zip(dict.get_ref(&keys.y).try_to_vector2()) + .zip(dict.get_ref(&keys.origin).try_to_vector2()) + .map(|((x, y), origin)| Transform2D { x, y, origin }.to_variant()) + } else { + None + } + } + } else { + None + } +} + +/// Quat field names. +static QUAT_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.x, &keys.y, &keys.z, &keys.w] + .iter() + .collect::>() + .into_shared() +}); + +/// Color field names +static COLOR_KEYS: Lazy> = Lazy::new(|| { + let keys = &*KEYS; + [&keys.r, &keys.g, &keys.b, &keys.a] + .iter() + .collect::>() + .into_shared() +}); + +/// `dict` has been verified to contain 4 entries. +/// +/// This function checks the keys to determine which Variant type to return, and verifies the types +/// of the values before copying them into the correct core type and converting it to a Variant. +fn quat_or_color(dict: &Dictionary) -> Option { + unsafe { + //SAFETY: `dict` is Unique, so it shouldn't be modified through another reference, + // we verify that all keys exist before calling `get_ref`, so the dictionary won't re-allocate, + // and we won't be returning any references, so they can't be invalidated later. + if dict.contains_all(&*QUAT_KEYS) { + let keys = &*KEYS; + get_f32(dict, &keys.x) + .zip(get_f32(dict, &keys.y)) + .zip(get_f32(dict, &keys.z)) + .zip(get_f32(dict, &keys.w)) + .map(|(((x, y), z), w)| Quat { x, y, z, w }.to_variant()) + } else if dict.contains_all(&*COLOR_KEYS) { + let keys = &*KEYS; + get_f32(dict, &keys.r) + .zip(get_f32(dict, &keys.g)) + .zip(get_f32(dict, &keys.b)) + .zip(get_f32(dict, &keys.a)) + .map(|(((r, g), b), a)| Color { r, g, b, a }.to_variant()) + } else { + None + } + } +} + +/// Get the value corresponding to `key` as an `f32` if it's a number. +/// +/// # Safety +/// This calls `Dictionary::get_ref`, so either `key` must exist, or there must be no other +/// references to values in this dictionary still in use, otherwise Godot may re-allocate the +/// dictionary and invalidate any other references. +unsafe fn get_f32(dict: &Dictionary, key: &Variant) -> Option { + f32_field(dict.get_ref(&key)) +} + +/// Tries to cast the value to an f32 first by checking if it's an f64, then checking if it's an i64 +/// (so users and formats can leave the `.0` off of whole-number floats. +fn f32_field(v: &Variant) -> Option { + v.try_to_f64() + .map(|f| f as f32) + .or_else(|| v.try_to_i64().map(|i| i as f32)) +} diff --git a/gdnative-core/src/core_types/variant_array.rs b/gdnative-core/src/core_types/variant_array.rs index 48870fa02..049e5297b 100644 --- a/gdnative-core/src/core_types/variant_array.rs +++ b/gdnative-core/src/core_types/variant_array.rs @@ -588,6 +588,67 @@ impl Extend for VariantArray } } +#[cfg(feature = "serde")] +pub(super) mod serde { + use super::*; + use ::serde::{ + de::{SeqAccess, Visitor}, + ser::SerializeSeq, + Deserialize, Deserializer, Serialize, Serializer, + }; + use std::fmt::Formatter; + + impl Serialize for VariantArray { + #[inline] + fn serialize(&self, ser: S) -> Result + where + S: Serializer, + { + let mut ser = ser.serialize_seq(Some(self.len() as usize))?; + for v in self.iter() { + ser.serialize_element(&v)? + } + ser.end() + } + } + + pub(in super::super) struct VariantArrayVisitor; + + impl<'de> Visitor<'de> for VariantArrayVisitor { + type Value = VariantArray; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a VariantArray") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let arr = VariantArray::new(); + if let Some(size) = seq.size_hint() { + arr.resize(size as i32); + } + while let Some(val) = seq.next_element::()? { + arr.push(val) + } + Ok(arr) + } + } + + impl<'de, Access: ThreadAccess> Deserialize<'de> for VariantArray { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer + .deserialize_seq(VariantArrayVisitor) + .map(|arr| unsafe { arr.cast_access() }) + } + } +} + godot_test!(test_array { let foo = Variant::from_str("foo"); let bar = Variant::from_str("bar"); 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 578abbfc9..08721c991 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 0bb21ea70..56c60dc18 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..a8a935ec4 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -16,3 +16,7 @@ type_tag_fallback = ["gdnative/type_tag_fallback"] gdnative = { path = "../gdnative", features = ["gd_test"] } gdnative-derive = { path = "../gdnative-derive" } approx = "0.5.0" +ron = "0.6.4" +serde = "1" +serde_json = "1.0.64" +bincode = "1.3.3" \ No newline at end of file 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..5c9e5f9a3 --- /dev/null +++ b/test/src/test_serde.rs @@ -0,0 +1,203 @@ +use ::serde::{Deserialize, Serialize}; +use gdnative::prelude::*; + +#[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, //TODO: PartialEq + vec3: Vector3, + // xform_2d: Transform2D, //TODO: PartialEq + plane: Plane, + quat: Quat, + aabb: Aabb, + basis: Basis, + xform: Transform, + color: Color, + path: NodePath, + rid: Rid, + // obj: Object, //TODO: how best to test this? + // 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 str".into(), + vec2: Vector2::RIGHT, + vec3: Vector3::BACK, + 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(), + rid: Rid::new(), + 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, + Vector3::FORWARD, + ]), + color_arr: ColorArray::from_slice(&[Color::from_rgba(0.0, 1.0, 0.627, 0.8)]), + } + } +} + +pub(crate) fn run_tests() -> bool { + let mut status = true; + + //These [de]serialize each field individually, instead of going through ToVariant/FromVariant + status &= test_ron_round_trip(); + status &= test_json_round_trip(); + + let mut eq_works = true; + eq_works &= test_variant_eq(); + eq_works &= test_dispatch_eq(); + //All other tests depend on these invariants + if !eq_works { + gdnative::godot_error!( + " !!!! Can't run remaining serde tests, ToVariant/FromVariant is broken!" + ); + return false; + } + + status &= test_bincode_round_trip(); + + status +} + +/// 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 test = Foo::new(); + let variant = test.to_variant(); + let test_again = Foo::from_variant(&variant).unwrap(); + assert_eq!(test, test_again); + }) + .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_variant_eq"); + + let ok = std::panic::catch_unwind(|| { + let test = Foo::new(); + let dispatch = test.to_variant().dispatch(); + let test_again = Foo::from_variant(&Variant::from(&dispatch)).unwrap(); + assert_eq!(test, test_again); + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_dispatch_eq failed"); + } + + ok +} + +fn test_ron_round_trip() -> bool { + println!(" -- test_ron_round_trip"); + + let ok = std::panic::catch_unwind(|| { + let test = Foo::new(); + let test_str = ron::to_string(&test); + let mut de = ron::Deserializer::from_str(test_str.as_ref().unwrap()); + let test_again = Foo::deserialize(de.as_mut().unwrap()).unwrap(); + assert_eq!(test, test_again) + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_ron_round_trip failed"); + } + + ok +} + +fn test_json_round_trip() -> bool { + println!(" -- test_json_round_trip"); + + let ok = std::panic::catch_unwind(|| { + let test = Foo::new(); + let test_str = serde_json::to_string(&test); + let test_again = serde_json::from_str::(test_str.as_ref().unwrap()).unwrap(); + assert_eq!(test, test_again) + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_json_round_trip failed"); + } + + ok +} + +fn test_bincode_round_trip() -> bool { + println!(" -- test_bincode_round_trip"); + + let ok = std::panic::catch_unwind(|| { + let test = Foo::new(); + let test_bytes = bincode::serialize(&test.to_variant().dispatch()); + let disp = bincode::deserialize::(test_bytes.as_ref().unwrap()).unwrap(); + let test_again = Foo::from_variant(&Variant::from(&disp)).unwrap(); + assert_eq!(test, test_again) + }) + .is_ok(); + + if !ok { + gdnative::godot_error!(" !! Test test_bincode_round_trip failed"); + } + + ok +}