Skip to content

Commit 5673b60

Browse files
committed
Add GodotObject serialization support
1 parent f541b93 commit 5673b60

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

gdnative-bindings/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ gdnative-sys = { path = "../gdnative-sys", version = "0.9.3" }
1919
gdnative-core = { path = "../gdnative-core", version = "=0.9.3" }
2020
libc = "0.2"
2121
bitflags = "1.2"
22+
serde = { version = "1.0", default_features = false, features = ["derive"], optional = true }
2223

2324
[build-dependencies]
2425
heck = "0.3.0"

gdnative-bindings/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ pub use generated::*;
1111
pub mod utils;
1212

1313
pub(crate) mod icalls;
14+
15+
#[cfg(feature = "serde")]
16+
mod serde;
17+
#[cfg(feature = "serde")]
18+
use serde::{DeserializedObject, SerializableObject};

gdnative-bindings/src/serde.rs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use crate::Object;
2+
use gdnative_core::core_types::{GodotString, Variant};
3+
use gdnative_core::object::{Instanciable, RefImplBound, SafeDeref, SubClass};
4+
use gdnative_core::thread_access::Unique;
5+
use gdnative_core::{GodotObject, NewRef, Ref};
6+
use serde::de::{MapAccess, Visitor};
7+
use serde::ser::SerializeStruct;
8+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
9+
use std::fmt::Formatter;
10+
use std::marker::PhantomData;
11+
use std::ops::Deref;
12+
13+
//TODO: Optimize (by adding names to a static HashSet?) instead of leaking so many strings
14+
15+
impl Serialize for Object {
16+
#[inline]
17+
fn serialize<S>(&self, ser: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
18+
where
19+
S: Serializer,
20+
{
21+
let name = Box::leak(self.get_class().to_string().into_boxed_str());
22+
let arr = self.get_property_list();
23+
let mut ser = ser.serialize_struct(name, arr.len() as usize)?;
24+
for dict in arr.iter().map(|v| v.to_dictionary()) {
25+
let name = dict.get("name").to_godot_string();
26+
let value = self.get(name.new_ref());
27+
let name = Box::leak(name.to_string().into_boxed_str());
28+
ser.serialize_field(&*name, &value)?
29+
}
30+
ser.end()
31+
}
32+
}
33+
34+
/// Newtype that allows serializing all properties of an instance of a Godot class, skipping
35+
/// properties that are set to their default values. This can be used in conjunction with an
36+
/// [EditorImportPlugin] or custom engine module to mimic Godot's `tres` files while using a
37+
/// different syntax.
38+
pub struct SerializableObject<'a, T: GodotObject>(&'a T);
39+
40+
// Unfortunately, Rust's orphan rules won't allow us to `impl Deserialize` directly for `&T`,
41+
// and `Object` is defined in this crate, not in `gdnative_core`. Thus we can't add `SubClass<Object>`
42+
// bounds in `gdnative_core`, and we can only `impl Serialize` on a newtype here.
43+
impl<'a, T: Instanciable + SubClass<Object>> Serialize for SerializableObject<'a, T>
44+
where
45+
RefImplBound: SafeDeref<T::RefKind, Unique>,
46+
{
47+
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
48+
where
49+
S: Serializer,
50+
{
51+
let obj = self.0.upcast::<Object>();
52+
let name = Box::leak(obj.get_class().to_string().into_boxed_str());
53+
let default = T::new();
54+
55+
let mut fields = Vec::new();
56+
57+
for v in obj.get_property_list().iter() {
58+
let dict = v.to_dictionary();
59+
let gd_name = dict.get("name").to_godot_string();
60+
let value = obj.get(gd_name.new_ref());
61+
let def_val = default.as_ref().upcast::<Object>().get(gd_name.new_ref());
62+
if value != def_val {
63+
let name = &*Box::leak(gd_name.to_string().into_boxed_str());
64+
fields.push((name, value));
65+
}
66+
}
67+
let mut ser = serializer.serialize_struct(name, fields.len())?;
68+
for (name, value) in &fields {
69+
ser.serialize_field(*name, value)?
70+
}
71+
ser.end()
72+
}
73+
}
74+
75+
/// Unfortunately, Rust's orphan rules won't allow us to `impl Deserialize for Ref<T, Unique>` from
76+
/// this crate, and `Object` is defined in this crate, not in `gdnative_core`.
77+
/// Thus we can't add `SubClass<Object>` bounds in `gdnative_core`, and we can only implement
78+
/// `Deserialize` on a newtype here.
79+
pub struct DeserializedObject<T: GodotObject>(pub Ref<T, Unique>);
80+
81+
impl<T: GodotObject> Deref for DeserializedObject<T> {
82+
type Target = Ref<T, Unique>;
83+
84+
#[inline]
85+
fn deref(&self) -> &Self::Target {
86+
&self.0
87+
}
88+
}
89+
90+
impl<'de, T: Instanciable + SubClass<Object>> Deserialize<'de> for DeserializedObject<T>
91+
where
92+
RefImplBound: SafeDeref<T::RefKind, Unique>,
93+
{
94+
#[inline]
95+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96+
where
97+
D: Deserializer<'de>,
98+
{
99+
struct ObjectVisitor<T>(PhantomData<T>);
100+
101+
impl<'de, T: Instanciable + SubClass<Object>> Visitor<'de> for ObjectVisitor<T>
102+
where
103+
RefImplBound: SafeDeref<T::RefKind, Unique>,
104+
{
105+
type Value = Ref<T, Unique>;
106+
107+
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
108+
write!(
109+
formatter,
110+
"struct {}",
111+
T::new().as_ref().upcast::<Object>().get_class()
112+
)
113+
}
114+
115+
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
116+
where
117+
A: MapAccess<'de>,
118+
{
119+
let t = T::new();
120+
let obj = t.as_ref().upcast::<Object>();
121+
while let Some((key, value)) = map.next_entry::<GodotString, Variant>()? {
122+
obj.set(key, value);
123+
}
124+
125+
Ok(t)
126+
}
127+
}
128+
129+
let t = T::new();
130+
131+
let name = Box::leak(
132+
t.as_ref()
133+
.upcast::<Object>()
134+
.get_class()
135+
.to_string()
136+
.into_boxed_str(),
137+
);
138+
139+
let fields = unsafe {
140+
&*Box::leak(
141+
t.as_ref()
142+
.upcast::<Object>()
143+
.get_property_list()
144+
.iter()
145+
.map(|v| {
146+
let name = v.to_dictionary().get("name").to_string();
147+
&*Box::leak(name.into_boxed_str())
148+
})
149+
.collect::<Vec<_>>()
150+
.into_boxed_slice(),
151+
)
152+
};
153+
154+
deserializer
155+
.deserialize_struct(name, fields, ObjectVisitor::<T>(PhantomData))
156+
.map(DeserializedObject)
157+
}
158+
}

gdnative/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ edition = "2018"
1414
[features]
1515
default = ["bindings"]
1616
formatted = ["gdnative-bindings/formatted", "gdnative-bindings/one_class_one_file"]
17-
serde = ["gdnative-core/serde"]
17+
serde = ["gdnative-core/serde", "gdnative-bindings/serde"]
1818

1919
gd_test = ["gdnative-core/gd_test"]
2020
type_tag_fallback = ["gdnative-core/type_tag_fallback"]

0 commit comments

Comments
 (0)