gdharness 0.13.2 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,13 @@
1
1
  extends RefCounted
2
2
 
3
+ ## Godot values as JSON can carry them, and JSON back into the values the engine wants.
4
+ ##
5
+ ## This file is the one beside the operations, and `bun run sync:gd` copies it into each addon,
6
+ ## because an addon is installed as a directory and cannot preload out of one. The copies are
7
+ ## byte for byte this file and a fixture says so. Three spellings of one value is what there was
8
+ ## before: the same Vector2 came back three ways depending on which tool was asked, and a caller
9
+ ## reading a property off the editor could not feed it back to the game.
10
+
3
11
  const Read = preload("reading.gd")
4
12
 
5
13
  # What each Godot type becomes on the wire, as one table you can read rather than an order you
@@ -10,20 +18,97 @@ const Read = preload("reading.gd")
10
18
  # Method names rather than Callables: a table of Callables bound to this object is a reference
11
19
  # cycle that nothing breaks, so the object outlives the run and the engine reports its script as
12
20
  # a resource still in use at exit, on the same stderr the server reads failures off.
21
+ #
22
+ # Every type the engine has that JSON cannot carry is in here, and a fixture walks the engine's own
23
+ # type list to say so, because what this table used to be was the types somebody had thought of.
24
+ # JSON does not refuse what it cannot carry: it writes the value's own text and moves on, so a
25
+ # Polygon2D's points came back as the string "[(1.0, 2.0), (3.0, 4.0)]" and a Quaternion as
26
+ # "(0, 0, 0, 1)". Both read as an answer, neither can be read back, and nothing anywhere said the
27
+ # value had been flattened on the way out.
28
+ #
29
+ # The types JSON does carry exactly are left alone, PackedStringArray and the numeric packed arrays
30
+ # among them, because a plugin list reads better as ["res://addons/x/plugin.cfg"] than as a tagged
31
+ # wrapper around one, and type_convert restores the exact type wherever the receiver knows which
32
+ # type it wanted.
13
33
  const SERIALISERS: Dictionary = {
14
34
  TYPE_NIL: "_serialize_nil",
15
35
  TYPE_VECTOR2: "_serialize_vector2",
16
- TYPE_VECTOR3: "_serialize_vector3",
17
36
  TYPE_VECTOR2I: "_serialize_vector2i",
37
+ TYPE_VECTOR3: "_serialize_vector3",
18
38
  TYPE_VECTOR3I: "_serialize_vector3i",
19
- TYPE_COLOR: "_serialize_color",
20
- TYPE_NODE_PATH: "_serialize_node_path",
21
- TYPE_ARRAY: "_serialize_array",
39
+ TYPE_VECTOR4: "_serialize_vector4",
40
+ TYPE_VECTOR4I: "_serialize_vector4i",
22
41
  TYPE_RECT2: "_serialize_rect2",
42
+ TYPE_RECT2I: "_serialize_rect2i",
43
+ TYPE_PLANE: "_serialize_plane",
44
+ TYPE_QUATERNION: "_serialize_quaternion",
45
+ TYPE_AABB: "_serialize_aabb",
46
+ TYPE_BASIS: "_serialize_basis",
23
47
  TYPE_TRANSFORM2D: "_serialize_transform2d",
24
48
  TYPE_TRANSFORM3D: "_serialize_transform3d",
25
- TYPE_DICTIONARY: "_serialize_dictionary",
49
+ TYPE_PROJECTION: "_serialize_projection",
50
+ TYPE_COLOR: "_serialize_color",
51
+ TYPE_NODE_PATH: "_serialize_node_path",
52
+ TYPE_RID: "_serialize_rid",
26
53
  TYPE_OBJECT: "_serialize_object",
54
+ TYPE_CALLABLE: "_serialize_callable",
55
+ TYPE_SIGNAL: "_serialize_signal",
56
+ TYPE_DICTIONARY: "_serialize_dictionary",
57
+ TYPE_ARRAY: "_serialize_array",
58
+ TYPE_PACKED_BYTE_ARRAY: "_serialize_bytes",
59
+ TYPE_PACKED_VECTOR2_ARRAY: "_serialize_vector2s",
60
+ TYPE_PACKED_VECTOR3_ARRAY: "_serialize_vector3s",
61
+ TYPE_PACKED_VECTOR4_ARRAY: "_serialize_vector4s",
62
+ TYPE_PACKED_COLOR_ARRAY: "_serialize_colors",
63
+ }
64
+
65
+ # What each tag builds back into. The other half of the table above, and separate from it because
66
+ # they are keyed differently: one by the type of a value in hand, the other by the name in a
67
+ # dictionary that arrived from somewhere else.
68
+ const BUILDERS: Dictionary = {
69
+ "Vector2": "_build_vector2",
70
+ "Vector2i": "_build_vector2i",
71
+ "Vector3": "_build_vector3",
72
+ "Vector3i": "_build_vector3i",
73
+ "Vector4": "_build_vector4",
74
+ "Vector4i": "_build_vector4i",
75
+ "Rect2": "_build_rect2",
76
+ "Rect2i": "_build_rect2i",
77
+ "Plane": "_build_plane",
78
+ "Quaternion": "_build_quaternion",
79
+ "AABB": "_build_aabb",
80
+ "Basis": "_build_basis",
81
+ "Transform2D": "_build_transform2d",
82
+ "Transform3D": "_build_transform3d",
83
+ "Projection": "_build_projection",
84
+ "Color": "_build_color",
85
+ "NodePath": "_build_node_path",
86
+ "PackedByteArray": "_build_bytes",
87
+ }
88
+
89
+ # What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
90
+ # tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
91
+ # answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
92
+ # comes back as it arrived, and whoever asked can read it against the type they were expecting.
93
+ const REQUIRED: Dictionary = {
94
+ "Vector2": [["x", "y"]],
95
+ "Vector2i": [["x", "y"]],
96
+ "Vector3": [["x", "y", "z"]],
97
+ "Vector3i": [["x", "y", "z"]],
98
+ "Vector4": [["x", "y", "z", "w"]],
99
+ "Vector4i": [["x", "y", "z", "w"]],
100
+ "Rect2": [["position", "size"], ["x", "y", "width", "height"]],
101
+ "Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
102
+ "Plane": [["normal", "d"]],
103
+ "Quaternion": [["x", "y", "z", "w"]],
104
+ "AABB": [["position", "size"]],
105
+ "Basis": [["x", "y", "z"]],
106
+ "Transform2D": [["x", "y", "origin"]],
107
+ "Transform3D": [["basis", "origin"]],
108
+ "Projection": [["x", "y", "z", "w"]],
109
+ "Color": [["r", "g", "b"]],
110
+ "NodePath": [["path"]],
111
+ "PackedByteArray": [["base64"]],
27
112
  }
28
113
 
29
114
 
@@ -43,6 +128,10 @@ func serialize_value(value: Variant) -> Variant:
43
128
 
44
129
 
45
130
  # Rebuilds a Godot value from the shape serialize_value gave it.
131
+ #
132
+ # A tag with no builder is left as the dictionary it arrived as. Some of them describe something
133
+ # that cannot be rebuilt at all, a Callable or a live object, and answering with a default-built
134
+ # one of those would be inventing a value rather than reporting one.
46
135
  func deserialize_value(value: Variant) -> Variant:
47
136
  if value == null:
48
137
  return null
@@ -57,59 +146,34 @@ func deserialize_value(value: Variant) -> Variant:
57
146
 
58
147
  var fields: Dictionary = value
59
148
  if not fields.has("_type"):
60
- var rebuilt: Dictionary = {}
149
+ var mapped: Dictionary = {}
61
150
  for key: Variant in fields:
62
- rebuilt[key] = deserialize_value(fields[key])
63
- return rebuilt
151
+ mapped[key] = deserialize_value(fields[key])
152
+ return mapped
64
153
 
65
154
  # Each component read as the type the constructor wants before it is handed over. These come
66
155
  # out of JSON as Variant, and a project holding `unsafe_call_argument` at error level refuses
67
156
  # to compile a script that gives a Variant to a typed parameter: the operations are compiled
68
157
  # under the target project's warning levels, so one such project lost every headless call.
69
- match fields["_type"]:
70
- "Vector2":
71
- return Vector2(Read.as_float(fields.get("x", 0)), Read.as_float(fields.get("y", 0)))
72
- "Vector3":
73
- return Vector3(
74
- Read.as_float(fields.get("x", 0)),
75
- Read.as_float(fields.get("y", 0)),
76
- Read.as_float(fields.get("z", 0))
77
- )
78
- "Vector2i":
79
- return Vector2i(Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)))
80
- "Vector3i":
81
- return Vector3i(
82
- Read.as_int(fields.get("x", 0)),
83
- Read.as_int(fields.get("y", 0)),
84
- Read.as_int(fields.get("z", 0))
85
- )
86
- "Color":
87
- return Color(
88
- Read.as_float(fields.get("r", 0)),
89
- Read.as_float(fields.get("g", 0)),
90
- Read.as_float(fields.get("b", 0)),
91
- Read.as_float(fields.get("a", 1), 1.0)
92
- )
93
- "Rect2":
94
- return _rect_from(fields)
95
- "NodePath":
96
- return NodePath(str(fields.get("path", "")))
97
- return fields
98
-
99
-
100
- ## A Rect2 out of its two tagged corners, or the fields back when they do not describe one.
101
- ##
102
- ## Its own function because the components have to be read as Vector2 before the constructor is
103
- ## given them, and a branch that can also answer with the fields would push the match above past
104
- ## the return count this project holds itself to.
105
- func _rect_from(fields: Dictionary) -> Variant:
106
- var position: Variant = deserialize_value(fields.get("position", {}))
107
- var size: Variant = deserialize_value(fields.get("size", {}))
108
- if not (position is Vector2 and size is Vector2):
158
+ var tag: String = str(fields["_type"])
159
+ var builder: String = BUILDERS.get(tag, "")
160
+ var needed: Array = REQUIRED.get(tag, [])
161
+ if builder.is_empty() or not _carries(fields, needed):
109
162
  return fields
110
- var at: Vector2 = position
111
- var extent: Vector2 = size
112
- return Rect2(at, extent)
163
+ return call(builder, fields)
164
+
165
+
166
+ ## Whether [param fields] carries every key of any one of [param alternatives].
167
+ ##
168
+ ## More than one spelling because a rectangle has two: the two corners it is answered with, and the
169
+ ## four numbers the editor's own tools answered with before the serialisers were made one, which a
170
+ ## caller may have kept.
171
+ func _carries(fields: Dictionary, alternatives: Array) -> bool:
172
+ for alternative: Variant in alternatives:
173
+ var keys: Array = alternative
174
+ if keys.all(func(key: Variant) -> bool: return fields.has(key)):
175
+ return true
176
+ return false
113
177
 
114
178
 
115
179
  func _serialize_nil(_value: Variant) -> Variant:
@@ -120,28 +184,24 @@ func _serialize_vector2(value: Vector2) -> Dictionary:
120
184
  return {"x": value.x, "y": value.y, "_type": "Vector2"}
121
185
 
122
186
 
123
- func _serialize_vector3(value: Vector3) -> Dictionary:
124
- return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3"}
125
-
126
-
127
187
  func _serialize_vector2i(value: Vector2i) -> Dictionary:
128
188
  return {"x": value.x, "y": value.y, "_type": "Vector2i"}
129
189
 
130
190
 
131
- func _serialize_vector3i(value: Vector3i) -> Dictionary:
132
- return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3i"}
191
+ func _serialize_vector3(value: Vector3) -> Dictionary:
192
+ return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3"}
133
193
 
134
194
 
135
- func _serialize_color(value: Color) -> Dictionary:
136
- return {"r": value.r, "g": value.g, "b": value.b, "a": value.a, "_type": "Color"}
195
+ func _serialize_vector3i(value: Vector3i) -> Dictionary:
196
+ return {"x": value.x, "y": value.y, "z": value.z, "_type": "Vector3i"}
137
197
 
138
198
 
139
- func _serialize_node_path(value: NodePath) -> Dictionary:
140
- return {"path": str(value), "_type": "NodePath"}
199
+ func _serialize_vector4(value: Vector4) -> Dictionary:
200
+ return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4"}
141
201
 
142
202
 
143
- func _serialize_array(value: Array) -> Array:
144
- return value.map(serialize_value)
203
+ func _serialize_vector4i(value: Vector4i) -> Dictionary:
204
+ return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Vector4i"}
145
205
 
146
206
 
147
207
  func _serialize_rect2(value: Rect2) -> Dictionary:
@@ -150,6 +210,33 @@ func _serialize_rect2(value: Rect2) -> Dictionary:
150
210
  }
151
211
 
152
212
 
213
+ func _serialize_rect2i(value: Rect2i) -> Dictionary:
214
+ return {
215
+ "position": serialize_value(value.position), "size": serialize_value(value.size), "_type": "Rect2i"
216
+ }
217
+
218
+
219
+ func _serialize_plane(value: Plane) -> Dictionary:
220
+ return {"normal": serialize_value(value.normal), "d": value.d, "_type": "Plane"}
221
+
222
+
223
+ func _serialize_quaternion(value: Quaternion) -> Dictionary:
224
+ return {"x": value.x, "y": value.y, "z": value.z, "w": value.w, "_type": "Quaternion"}
225
+
226
+
227
+ func _serialize_aabb(value: AABB) -> Dictionary:
228
+ return {"position": serialize_value(value.position), "size": serialize_value(value.size), "_type": "AABB"}
229
+
230
+
231
+ func _serialize_basis(value: Basis) -> Dictionary:
232
+ return {
233
+ "x": serialize_value(value.x),
234
+ "y": serialize_value(value.y),
235
+ "z": serialize_value(value.z),
236
+ "_type": "Basis"
237
+ }
238
+
239
+
153
240
  func _serialize_transform2d(value: Transform2D) -> Dictionary:
154
241
  return {
155
242
  "origin": serialize_value(value.origin),
@@ -161,17 +248,52 @@ func _serialize_transform2d(value: Transform2D) -> Dictionary:
161
248
 
162
249
  func _serialize_transform3d(value: Transform3D) -> Dictionary:
163
250
  return {
164
- "origin": serialize_value(value.origin),
165
- "basis":
166
- {
167
- "x": serialize_value(value.basis.x),
168
- "y": serialize_value(value.basis.y),
169
- "z": serialize_value(value.basis.z)
170
- },
171
- "_type": "Transform3D"
251
+ "origin": serialize_value(value.origin), "basis": serialize_value(value.basis), "_type": "Transform3D"
172
252
  }
173
253
 
174
254
 
255
+ func _serialize_projection(value: Projection) -> Dictionary:
256
+ return {
257
+ "x": serialize_value(value.x),
258
+ "y": serialize_value(value.y),
259
+ "z": serialize_value(value.z),
260
+ "w": serialize_value(value.w),
261
+ "_type": "Projection"
262
+ }
263
+
264
+
265
+ func _serialize_color(value: Color) -> Dictionary:
266
+ return {"r": value.r, "g": value.g, "b": value.b, "a": value.a, "_type": "Color"}
267
+
268
+
269
+ func _serialize_node_path(value: NodePath) -> Dictionary:
270
+ return {"path": str(value), "_type": "NodePath"}
271
+
272
+
273
+ # An id rather than nothing, because two properties holding the same RID are worth telling apart
274
+ # even though neither can be handed back to the engine.
275
+ func _serialize_rid(value: RID) -> Dictionary:
276
+ return {"id": value.get_id(), "_type": "RID"}
277
+
278
+
279
+ # A bound method, said in the terms a caller could act on: what it is on, and what it is called.
280
+ # It cannot be rebuilt from here, and saying so is the answer rather than a silent null.
281
+ func _serialize_callable(value: Callable) -> Dictionary:
282
+ var described: Dictionary = {"method": str(value.get_method()), "_type": "Callable"}
283
+ var holder: Object = value.get_object()
284
+ if holder != null:
285
+ described["object"] = _describe_object(holder)
286
+ return described
287
+
288
+
289
+ func _serialize_signal(value: Signal) -> Dictionary:
290
+ var described: Dictionary = {"name": str(value.get_name()), "_type": "Signal"}
291
+ var holder: Object = value.get_object()
292
+ if holder != null:
293
+ described["object"] = _describe_object(holder)
294
+ return described
295
+
296
+
175
297
  func _serialize_dictionary(value: Dictionary) -> Dictionary:
176
298
  var serialised: Dictionary = {}
177
299
  for key: Variant in value:
@@ -179,11 +301,260 @@ func _serialize_dictionary(value: Dictionary) -> Dictionary:
179
301
  return serialised
180
302
 
181
303
 
304
+ func _serialize_array(value: Array) -> Array:
305
+ return value.map(serialize_value)
306
+
307
+
308
+ # Base64 rather than a list of numbers. A byte array is a texture, a sound or a save file often
309
+ # enough that the list would be the whole answer, megabytes of it, and unreadable either way.
310
+ func _serialize_bytes(value: PackedByteArray) -> Dictionary:
311
+ return {"base64": Marshalls.raw_to_base64(value), "size": value.size(), "_type": "PackedByteArray"}
312
+
313
+
314
+ # The packed vector and colour arrays, each element serialised the way one of them on its own is.
315
+ # A plain list rather than a tagged wrapper, so a polygon reads as the points it is, and because
316
+ # the receiver of a write knows the type it wanted and converts to it. One function each rather
317
+ # than one taking a Variant, because a Variant handed to the element serialiser is the unsafe
318
+ # argument a project with every warning at error level refuses to compile.
319
+ func _serialize_vector2s(value: PackedVector2Array) -> Array:
320
+ var serialised: Array = []
321
+ for element: Vector2 in value:
322
+ serialised.append(_serialize_vector2(element))
323
+ return serialised
324
+
325
+
326
+ func _serialize_vector3s(value: PackedVector3Array) -> Array:
327
+ var serialised: Array = []
328
+ for element: Vector3 in value:
329
+ serialised.append(_serialize_vector3(element))
330
+ return serialised
331
+
332
+
333
+ func _serialize_vector4s(value: PackedVector4Array) -> Array:
334
+ var serialised: Array = []
335
+ for element: Vector4 in value:
336
+ serialised.append(_serialize_vector4(element))
337
+ return serialised
338
+
339
+
340
+ func _serialize_colors(value: PackedColorArray) -> Array:
341
+ var serialised: Array = []
342
+ for element: Color in value:
343
+ serialised.append(_serialize_color(element))
344
+ return serialised
345
+
346
+
182
347
  # A pathless Resource says so rather than inventing an empty path for the caller to load.
183
348
  func _serialize_object(value: Object) -> Variant:
184
- if not value is Resource:
185
- return {"_type": "Object", "class": value.get_class()}
186
- var resource: Resource = value
187
- if resource.resource_path.is_empty():
188
- return {"_type": "Resource", "class": resource.get_class()}
189
- return {"path": resource.resource_path, "_type": "Resource", "class": resource.get_class()}
349
+ if value is Resource:
350
+ var resource: Resource = value
351
+ if resource.resource_path.is_empty():
352
+ return {"class": resource.get_class(), "_type": "Resource"}
353
+ return {"path": resource.resource_path, "class": resource.get_class(), "_type": "Resource"}
354
+ if value is Node:
355
+ var node: Node = value
356
+ if node.is_inside_tree():
357
+ return {"path": str(node.get_path()), "class": node.get_class(), "_type": "Node"}
358
+ return _describe_object(value)
359
+
360
+
361
+ ## A plain object, said in enough detail to tell it from the one beside it.
362
+ ##
363
+ ## `{"class": "RefCounted"}` was all of it, so a roster of twelve people came back as the word
364
+ ## RefCounted twelve times: correct, useless, and indistinguishable from a list of twelve of
365
+ ## anything else. The state worth looking at in a game is lists of these, so each one carries what
366
+ ## it is (the `class_name` its script declares, or the script's path when it has no name), what it
367
+ ## says about itself when the game has given it a `_to_string`, and an id that is at least
368
+ ## different per object. None of it is invented: every field is left out when there is nothing to
369
+ ## put in it, because a key holding "" is a worse answer than a key that is not there.
370
+ func _describe_object(value: Object) -> Dictionary:
371
+ var described: Dictionary = {"class": value.get_class(), "_type": "Object"}
372
+ # Asked as a Variant and narrowed, because get_script answers one and an object without a
373
+ # script answers null: casting that to Script is the unsafe cast the engine refuses to compile.
374
+ var attached: Variant = value.get_script()
375
+ if attached is Script:
376
+ var script: Script = attached
377
+ var declared: String = String(script.get_global_name())
378
+ if not declared.is_empty():
379
+ described["script_class"] = declared
380
+ elif not script.resource_path.is_empty():
381
+ described["script"] = script.resource_path
382
+ # Only when the game wrote one. The default is "<RefCounted#31098236>", which is the id again
383
+ # in a costume and reads as if the object had been asked and had nothing to say.
384
+ if value.has_method("_to_string"):
385
+ described["says"] = value.to_string()
386
+ described["id"] = value.get_instance_id()
387
+ return described
388
+
389
+
390
+ func _build_vector2(fields: Dictionary) -> Vector2:
391
+ return Vector2(Read.as_float(fields.get("x", 0)), Read.as_float(fields.get("y", 0)))
392
+
393
+
394
+ func _build_vector2i(fields: Dictionary) -> Vector2i:
395
+ return Vector2i(Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)))
396
+
397
+
398
+ func _build_vector3(fields: Dictionary) -> Vector3:
399
+ return Vector3(
400
+ Read.as_float(fields.get("x", 0)),
401
+ Read.as_float(fields.get("y", 0)),
402
+ Read.as_float(fields.get("z", 0))
403
+ )
404
+
405
+
406
+ func _build_vector3i(fields: Dictionary) -> Vector3i:
407
+ return Vector3i(
408
+ Read.as_int(fields.get("x", 0)), Read.as_int(fields.get("y", 0)), Read.as_int(fields.get("z", 0))
409
+ )
410
+
411
+
412
+ func _build_vector4(fields: Dictionary) -> Vector4:
413
+ return Vector4(
414
+ Read.as_float(fields.get("x", 0)),
415
+ Read.as_float(fields.get("y", 0)),
416
+ Read.as_float(fields.get("z", 0)),
417
+ Read.as_float(fields.get("w", 0))
418
+ )
419
+
420
+
421
+ func _build_vector4i(fields: Dictionary) -> Vector4i:
422
+ return Vector4i(
423
+ Read.as_int(fields.get("x", 0)),
424
+ Read.as_int(fields.get("y", 0)),
425
+ Read.as_int(fields.get("z", 0)),
426
+ Read.as_int(fields.get("w", 0))
427
+ )
428
+
429
+
430
+ ## A rectangle from its two corners, or from the four numbers this tool answered with before the
431
+ ## serialisers were made one, so an answer somebody kept can still be written back.
432
+ func _build_rect2(fields: Dictionary) -> Rect2:
433
+ if not fields.has("position"):
434
+ return Rect2(
435
+ Read.as_float(fields.get("x", 0)),
436
+ Read.as_float(fields.get("y", 0)),
437
+ Read.as_float(fields.get("width", 0)),
438
+ Read.as_float(fields.get("height", 0))
439
+ )
440
+ return Rect2(_vector2_at(fields, "position"), _vector2_at(fields, "size"))
441
+
442
+
443
+ func _build_rect2i(fields: Dictionary) -> Rect2i:
444
+ if not fields.has("position"):
445
+ return Rect2i(
446
+ Read.as_int(fields.get("x", 0)),
447
+ Read.as_int(fields.get("y", 0)),
448
+ Read.as_int(fields.get("width", 0)),
449
+ Read.as_int(fields.get("height", 0))
450
+ )
451
+ return Rect2i(_vector2i_at(fields, "position"), _vector2i_at(fields, "size"))
452
+
453
+
454
+ func _build_plane(fields: Dictionary) -> Plane:
455
+ return Plane(_vector3_at(fields, "normal"), Read.as_float(fields.get("d", 0)))
456
+
457
+
458
+ func _build_quaternion(fields: Dictionary) -> Quaternion:
459
+ return Quaternion(
460
+ Read.as_float(fields.get("x", 0)),
461
+ Read.as_float(fields.get("y", 0)),
462
+ Read.as_float(fields.get("z", 0)),
463
+ Read.as_float(fields.get("w", 0))
464
+ )
465
+
466
+
467
+ func _build_aabb(fields: Dictionary) -> AABB:
468
+ return AABB(_vector3_at(fields, "position"), _vector3_at(fields, "size"))
469
+
470
+
471
+ func _build_basis(fields: Dictionary) -> Basis:
472
+ return Basis(
473
+ _vector3_at(fields, "x", Vector3.RIGHT),
474
+ _vector3_at(fields, "y", Vector3.UP),
475
+ _vector3_at(fields, "z", Vector3.BACK)
476
+ )
477
+
478
+
479
+ func _build_transform2d(fields: Dictionary) -> Transform2D:
480
+ return Transform2D(
481
+ _vector2_at(fields, "x", Vector2.RIGHT),
482
+ _vector2_at(fields, "y", Vector2.DOWN),
483
+ _vector2_at(fields, "origin")
484
+ )
485
+
486
+
487
+ func _build_transform3d(fields: Dictionary) -> Transform3D:
488
+ var held: Variant = fields.get("basis", null)
489
+ if not held is Dictionary:
490
+ return Transform3D(Basis.IDENTITY, _vector3_at(fields, "origin"))
491
+ var columns: Dictionary = held
492
+ return Transform3D(_build_basis(columns), _vector3_at(fields, "origin"))
493
+
494
+
495
+ func _build_projection(fields: Dictionary) -> Projection:
496
+ return Projection(
497
+ _vector4_at(fields, "x"), _vector4_at(fields, "y"), _vector4_at(fields, "z"), _vector4_at(fields, "w")
498
+ )
499
+
500
+
501
+ func _build_color(fields: Dictionary) -> Color:
502
+ return Color(
503
+ Read.as_float(fields.get("r", 0)),
504
+ Read.as_float(fields.get("g", 0)),
505
+ Read.as_float(fields.get("b", 0)),
506
+ Read.as_float(fields.get("a", 1), 1.0)
507
+ )
508
+
509
+
510
+ func _build_node_path(fields: Dictionary) -> NodePath:
511
+ return NodePath(str(fields.get("path", "")))
512
+
513
+
514
+ func _build_bytes(fields: Dictionary) -> PackedByteArray:
515
+ return Marshalls.base64_to_raw(str(fields.get("base64", "")))
516
+
517
+
518
+ ## One component of a compound value, as the type its constructor wants.
519
+ ##
520
+ ## Their own functions because a constructor refuses a Variant under `unsafe_call_argument`, and
521
+ ## because a component that is not there at all has to answer with a default rather than take the
522
+ ## whole build down with it.
523
+ ##
524
+ ## The component is built from its keys rather than deserialised, so it does not matter whether it
525
+ ## carries a tag. A caller writing a transform by hand writes {"x": {"x": 1, "y": 0}}, and that is
526
+ ## the same value as the tagged form this answers with.
527
+ ##
528
+ ## [param fallback] is what a missing component is, which is the zero vector everywhere except the
529
+ ## columns of a basis: a matrix half given should come out the identity it was being varied from
530
+ ## rather than the degenerate one that collapses everything drawn with it.
531
+ func _vector2_at(fields: Dictionary, key: String, fallback: Vector2 = Vector2.ZERO) -> Vector2:
532
+ var held: Variant = fields.get(key, null)
533
+ if not held is Dictionary:
534
+ return fallback
535
+ var component: Dictionary = held
536
+ return _build_vector2(component)
537
+
538
+
539
+ func _vector2i_at(fields: Dictionary, key: String, fallback: Vector2i = Vector2i.ZERO) -> Vector2i:
540
+ var held: Variant = fields.get(key, null)
541
+ if not held is Dictionary:
542
+ return fallback
543
+ var component: Dictionary = held
544
+ return _build_vector2i(component)
545
+
546
+
547
+ func _vector3_at(fields: Dictionary, key: String, fallback: Vector3 = Vector3.ZERO) -> Vector3:
548
+ var held: Variant = fields.get(key, null)
549
+ if not held is Dictionary:
550
+ return fallback
551
+ var component: Dictionary = held
552
+ return _build_vector3(component)
553
+
554
+
555
+ func _vector4_at(fields: Dictionary, key: String, fallback: Vector4 = Vector4.ZERO) -> Vector4:
556
+ var held: Variant = fields.get(key, null)
557
+ if not held is Dictionary:
558
+ return fallback
559
+ var component: Dictionary = held
560
+ return _build_vector4(component)