Skip to content

Commit d30fc84

Browse files
committed
Add serde support for GString, StringName, NodePath and Array
1 parent cf16a91 commit d30fc84

File tree

12 files changed

+332
-9
lines changed

12 files changed

+332
-9
lines changed

check.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ for arg in "$@"; do
171171
echo "$HELP_TEXT"
172172
exit 0
173173
;;
174+
--use-serde)
175+
extraCargoArgs+=("--features" "godot/serde")
176+
extraCargoArgs+=("--features" "serde")
177+
;;
174178
--double)
175179
extraCargoArgs+=("--features" "godot/double-precision")
176180
;;

godot-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ godot-ffi = { path = "../godot-ffi" }
2727
# See https://docs.rs/glam/latest/glam/index.html#feature-gates
2828
glam = { version = "0.23", features = ["debug-glam-assert"] }
2929
serde = { version = "1", features = ["derive"], optional = true }
30+
serde_json = { version = "1.0", optional = true }
3031

3132
# Reverse dev dependencies so doctests can use `godot::` prefix
3233
[dev-dependencies]

godot-core/src/builtin/array.rs

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,15 @@ impl<T: GodotType> Array<T> {
215215

216216
#[doc(hidden)]
217217
pub fn as_inner(&self) -> inner::InnerArray {
218-
// SAFETY: The memory layout of `TypedArray<T>` does not depend on `T`.
218+
// SAFETY: The memory layout of `Array<T>` does not depend on `T`.
219219
inner::InnerArray::from_outer_typed(self)
220220
}
221221

222222
/// Changes the generic type on this array, without changing its contents. Needed for API
223223
/// functions that return a variant array even though we know its type, and for API functions
224224
/// that take a variant array even though we want to pass a typed one.
225225
///
226-
/// This is marked `unsafe` since it can be used to break the invariant that a `TypedArray<T>`
226+
/// This is marked `unsafe` since it can be used to break the invariant that a `Array<T>`
227227
/// always holds a Godot array whose runtime type is `T`.
228228
///
229229
/// # Safety
@@ -236,7 +236,7 @@ impl<T: GodotType> Array<T> {
236236
/// In the current implementation, both cases will produce a panic rather than undefined
237237
/// behavior, but this should not be relied upon.
238238
unsafe fn assume_type<U: GodotType>(self) -> Array<U> {
239-
// SAFETY: The memory layout of `TypedArray<T>` does not depend on `T`.
239+
// SAFETY: The memory layout of `Array<T>` does not depend on `T`.
240240
unsafe { std::mem::transmute(self) }
241241
}
242242
}
@@ -276,7 +276,7 @@ impl<T: GodotType> Array<T> {
276276
///
277277
/// If specified, `step` is the relative index between source elements. It can be negative,
278278
/// in which case `begin` must be higher than `end`. For example,
279-
/// `TypedArray::from(&[0, 1, 2, 3, 4, 5]).slice(5, 1, -2)` returns `[5, 3]`.
279+
/// `Array::from(&[0, 1, 2, 3, 4, 5]).slice(5, 1, -2)` returns `[5, 3]`.
280280
///
281281
/// Array elements are copied to the slice, but any reference types (such as `Array`,
282282
/// `Dictionary` and `Object`) will still refer to the same value. To create a deep copy, use
@@ -292,7 +292,7 @@ impl<T: GodotType> Array<T> {
292292
///
293293
/// If specified, `step` is the relative index between source elements. It can be negative,
294294
/// in which case `begin` must be higher than `end`. For example,
295-
/// `TypedArray::from(&[0, 1, 2, 3, 4, 5]).slice(5, 1, -2)` returns `[5, 3]`.
295+
/// `Array::from(&[0, 1, 2, 3, 4, 5]).slice(5, 1, -2)` returns `[5, 3]`.
296296
///
297297
/// All nested arrays and dictionaries are duplicated and will not be shared with the original
298298
/// array. Note that any `Object`-derived elements will still be shallow copied. To create a
@@ -576,7 +576,7 @@ impl<T: GodotType + ToGodot> Array<T> {
576576
let len = self.len();
577577
assert!(
578578
index <= len,
579-
"TypedArray insertion index {index} is out of bounds: length is {len}",
579+
"Array insertion index {index} is out of bounds: length is {len}",
580580
);
581581
self.as_inner().insert(to_i64(index), value.to_variant());
582582
}
@@ -604,9 +604,9 @@ impl<T: GodotType + ToGodot> Array<T> {
604604
// but `[NAN] == [NAN]` is `true`. If they decide to make all NaNs equal, we can implement `Eq` and
605605
// `Ord`; if they decide to make all NaNs unequal, we can remove this comment.
606606
//
607-
// impl<T> Eq for TypedArray<T> {}
607+
// impl<T> Eq for Array<T> {}
608608
//
609-
// impl<T> Ord for TypedArray<T> {
609+
// impl<T> Ord for Array<T> {
610610
// ...
611611
// }
612612

@@ -1063,3 +1063,61 @@ impl fmt::Debug for TypeInfo {
10631063
write!(f, "{:?}{}", self.variant_type, class_str)
10641064
}
10651065
}
1066+
1067+
#[cfg(feature = "serde")]
1068+
mod serialize {
1069+
use super::*;
1070+
use serde::{
1071+
de::{SeqAccess, Visitor},
1072+
ser::SerializeSeq,
1073+
Deserialize, Deserializer, Serialize, Serializer,
1074+
};
1075+
use std::marker::PhantomData;
1076+
1077+
impl<T: Serialize + GodotType> Serialize for Array<T> {
1078+
#[inline]
1079+
fn serialize<S>(&self, ser: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
1080+
where
1081+
S: Serializer,
1082+
{
1083+
let mut ser = ser.serialize_seq(Some(self.len()))?;
1084+
for e in self.iter_shared() {
1085+
ser.serialize_element(&e)?
1086+
}
1087+
ser.end()
1088+
}
1089+
}
1090+
1091+
impl<'de, T: Deserialize<'de> + GodotType> Deserialize<'de> for Array<T> {
1092+
#[inline]
1093+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
1094+
where
1095+
D: Deserializer<'de>,
1096+
{
1097+
struct ArrayVisitor<T>(PhantomData<T>);
1098+
impl<'de, T: Deserialize<'de> + GodotType> Visitor<'de> for ArrayVisitor<T> {
1099+
type Value = Array<T>;
1100+
1101+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result {
1102+
formatter.write_str(std::any::type_name::<Self::Value>())
1103+
}
1104+
1105+
fn visit_seq<A>(
1106+
self,
1107+
mut seq: A,
1108+
) -> Result<Self::Value, <A as SeqAccess<'de>>::Error>
1109+
where
1110+
A: SeqAccess<'de>,
1111+
{
1112+
let mut vec = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);
1113+
while let Some(val) = seq.next_element::<T>()? {
1114+
vec.push(val);
1115+
}
1116+
Ok(Self::Value::from(vec.as_slice()))
1117+
}
1118+
}
1119+
1120+
deserializer.deserialize_seq(ArrayVisitor::<T>(PhantomData))
1121+
}
1122+
}
1123+
}

godot-core/src/builtin/string/gstring.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,53 @@ impl From<NodePath> for GString {
313313
Self::from(&path)
314314
}
315315
}
316+
317+
#[cfg(feature = "serde")]
318+
mod serialize {
319+
use super::*;
320+
use serde::{
321+
de::{Error, Visitor},
322+
Deserialize, Deserializer, Serialize, Serializer,
323+
};
324+
use std::fmt::Formatter;
325+
326+
impl Serialize for GString {
327+
#[inline]
328+
fn serialize<S>(
329+
&self,
330+
serializer: S,
331+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
332+
where
333+
S: Serializer,
334+
{
335+
serializer.serialize_str(&self.to_string())
336+
}
337+
}
338+
339+
#[cfg(feature = "serde")]
340+
impl<'de> serialize::Deserialize<'de> for GString {
341+
#[inline]
342+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
343+
where
344+
D: Deserializer<'de>,
345+
{
346+
struct GStringVisitor;
347+
impl<'de> Visitor<'de> for GStringVisitor {
348+
type Value = GString;
349+
350+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
351+
formatter.write_str("a GString")
352+
}
353+
354+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
355+
where
356+
E: Error,
357+
{
358+
Ok(GString::from(s))
359+
}
360+
}
361+
362+
deserializer.deserialize_str(GStringVisitor)
363+
}
364+
}
365+
}

godot-core/src/builtin/string/node_path.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,60 @@ impl From<StringName> for NodePath {
150150
Self::from(GString::from(string_name))
151151
}
152152
}
153+
154+
#[cfg(feature = "serde")]
155+
mod serialize {
156+
use super::*;
157+
use serde::{
158+
de::{Error, Visitor},
159+
Deserialize, Deserializer, Serialize, Serializer,
160+
};
161+
use std::fmt::Formatter;
162+
163+
impl Serialize for NodePath {
164+
#[inline]
165+
fn serialize<S>(&self, ser: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
166+
where
167+
S: Serializer,
168+
{
169+
ser.serialize_newtype_struct("NodePath", &*self.to_string())
170+
}
171+
}
172+
173+
impl<'de> Deserialize<'de> for NodePath {
174+
#[inline]
175+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176+
where
177+
D: Deserializer<'de>,
178+
{
179+
struct NodePathVisitor;
180+
181+
impl<'de> Visitor<'de> for NodePathVisitor {
182+
type Value = NodePath;
183+
184+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
185+
formatter.write_str("a NodePath")
186+
}
187+
188+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
189+
where
190+
E: Error,
191+
{
192+
Ok(NodePath::from(s))
193+
}
194+
195+
fn visit_newtype_struct<D>(
196+
self,
197+
deserializer: D,
198+
) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
199+
where
200+
D: Deserializer<'de>,
201+
{
202+
deserializer.deserialize_str(self)
203+
}
204+
}
205+
206+
deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor)
207+
}
208+
}
209+
}

godot-core/src/builtin/string/string_name.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,52 @@ impl From<NodePath> for StringName {
245245
Self::from(GString::from(path))
246246
}
247247
}
248+
249+
#[cfg(feature = "serde")]
250+
mod serialize {
251+
use super::*;
252+
use serde::{
253+
de::{Error, Visitor},
254+
Deserialize, Deserializer, Serialize, Serializer,
255+
};
256+
use std::fmt::Formatter;
257+
258+
impl Serialize for StringName {
259+
#[inline]
260+
fn serialize<S>(
261+
&self,
262+
serializer: S,
263+
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
264+
where
265+
S: Serializer,
266+
{
267+
serializer.serialize_str(&self.to_string())
268+
}
269+
}
270+
271+
impl<'de> serialize::Deserialize<'de> for StringName {
272+
#[inline]
273+
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
274+
where
275+
D: Deserializer<'de>,
276+
{
277+
struct StringNameVisitor;
278+
impl<'de> Visitor<'de> for StringNameVisitor {
279+
type Value = StringName;
280+
281+
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
282+
formatter.write_str("a StringName")
283+
}
284+
285+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
286+
where
287+
E: Error,
288+
{
289+
Ok(StringName::from(s))
290+
}
291+
}
292+
293+
deserializer.deserialize_str(StringNameVisitor)
294+
}
295+
}
296+
}

godot/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ default = ["codegen-full"]
1212
custom-godot = ["godot-core/custom-godot"]
1313
double-precision = ["godot-core/double-precision"]
1414
formatted = ["godot-core/codegen-fmt"]
15-
serde = ["godot-core/serde"]
15+
serde = ["godot-core/serde", "godot-core/serde_json"]
1616
lazy-function-tables = ["godot-core/codegen-lazy-fptrs"]
1717
experimental-threads = ["godot-core/experimental-threads"]
1818
experimental-godot-api = ["godot-core/experimental-godot-api"]

itest/rust/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ crate-type = ["cdylib"]
1010

1111
[features]
1212
default = []
13+
serde = ["dep:serde", "dep:serde_json"]
1314
# Do not add features here that are 1:1 forwarded to the `godot` crate.
1415
# Instead, compile itest with `--features godot/my-feature`.
1516

1617
[dependencies]
1718
godot = { path = "../../godot", default-features = false }
19+
serde = { version = "1", features = ["derive"], optional = true }
20+
serde_json = { version = "1.0", optional = true }
1821

1922
[build-dependencies]
2023
godot-bindings = { path = "../../godot-bindings" } # emit_godot_version_cfg

itest/rust/src/builtin_tests/containers/array_test.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,38 @@ impl ArrayTest {
460460
(1..(n + 1)).collect()
461461
}
462462
}
463+
464+
#[itest]
465+
#[cfg(feature = "serde")]
466+
fn serde_roundtrip() {
467+
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)]
468+
struct P {
469+
sequence: Array<i32>,
470+
names: Array<GString>,
471+
vectors: Array<Vector2i>,
472+
}
473+
let value = P {
474+
sequence: Array::from(&[1, 2, 3, 4, 5, 6]),
475+
names: Array::from(&[
476+
"Godot".into_godot(),
477+
"Rust".into_godot(),
478+
"Rocks".into_godot(),
479+
]),
480+
vectors: Array::from(&[
481+
Vector2i::new(1, 1),
482+
Vector2i::new(2, 2),
483+
Vector2i::new(3, 3),
484+
]),
485+
};
486+
487+
let expected_json = r#"{"sequence":[1,2,3,4,5,6],"names":["Godot","Rust","Rocks"],"vectors":[{"x":1,"y":1},{"x":2,"y":2},{"x":3,"y":3}]}"#;
488+
489+
let json: String = serde_json::to_string(&value).unwrap();
490+
let back: P = serde_json::from_str(json.as_str()).unwrap();
491+
492+
assert_eq!(back, value, "serde round-trip changes value");
493+
assert_eq!(
494+
json, expected_json,
495+
"value does not conform to expected JSON"
496+
);
497+
}

0 commit comments

Comments
 (0)