gdharness 1.3.2 → 1.3.4
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.
- package/build/cli.js +163 -85
- package/build/godot/addons/gdharness_editor/animation_reads.gd +14 -11
- package/build/godot/addons/gdharness_editor/declared_values.gd +157 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
- package/build/godot/addons/gdharness_editor/file_walk.gd +134 -0
- package/build/godot/addons/gdharness_editor/file_walk.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
- package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
- package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +249 -227
- package/build/godot/addons/gdharness_editor/reading.gd +15 -0
- package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
- package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +100 -15
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
- package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +402 -55
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +88 -5
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +102 -337
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +10 -26
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +30 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +14 -11
- package/build/godot/operations/declared_values.gd +157 -4
- package/build/godot/operations/dependencies.gd +14 -39
- package/build/godot/operations/editor_tools.gd +7 -3
- package/build/godot/operations/file_reads.gd +15 -3
- package/build/godot/operations/file_walk.gd +1 -0
- package/build/godot/operations/gdscript_analysis.gd +229 -75
- package/build/godot/operations/gdscript_authoring.gd +142 -34
- package/build/godot/operations/import_pipeline.gd +17 -6
- package/build/godot/operations/logger.gd +7 -0
- package/build/godot/operations/property_paths.gd +225 -0
- package/build/godot/operations/property_values.gd +249 -227
- package/build/godot/operations/reading.gd +15 -0
- package/build/godot/operations/same_values.gd +177 -0
- package/build/godot/operations/serialisation.gd +75 -3
- package/package.json +1 -1
|
@@ -15,7 +15,9 @@ extends RefCounted
|
|
|
15
15
|
## dictionary shaped like a vector or a bare pair of numbers can be read as one.
|
|
16
16
|
|
|
17
17
|
const GdhDeclared = preload("declared_values.gd")
|
|
18
|
+
const GdhPropertyPaths = preload("property_paths.gd")
|
|
18
19
|
const GdhRead = preload("reading.gd")
|
|
20
|
+
const GdhSame = preload("same_values.gd")
|
|
19
21
|
const GdhSerialisation = preload("serialisation.gd")
|
|
20
22
|
const GdhShapes = preload("value_shapes.gd")
|
|
21
23
|
|
|
@@ -46,23 +48,6 @@ const PACKED_ELEMENTS: Dictionary = {
|
|
|
46
48
|
TYPE_PACKED_COLOR_ARRAY: TYPE_COLOR,
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
# The types made of floats, which a read back compares within float precision: a Vector2 property
|
|
50
|
-
# stores 32-bit components, so 0.1 comes back as 0.100000001.
|
|
51
|
-
const FLOAT_SHAPES: Array[int] = [
|
|
52
|
-
TYPE_VECTOR2,
|
|
53
|
-
TYPE_VECTOR3,
|
|
54
|
-
TYPE_VECTOR4,
|
|
55
|
-
TYPE_QUATERNION,
|
|
56
|
-
TYPE_COLOR,
|
|
57
|
-
TYPE_RECT2,
|
|
58
|
-
TYPE_TRANSFORM2D,
|
|
59
|
-
TYPE_BASIS,
|
|
60
|
-
TYPE_TRANSFORM3D,
|
|
61
|
-
TYPE_PLANE,
|
|
62
|
-
TYPE_AABB,
|
|
63
|
-
TYPE_PROJECTION,
|
|
64
|
-
]
|
|
65
|
-
|
|
66
51
|
# Entries in a property list that only head a section of the inspector. Object.set ignores their
|
|
67
52
|
# names, so a write to "Visibility" or "Transform" changed nothing and was answered as made.
|
|
68
53
|
const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
@@ -92,7 +77,8 @@ var _outside_declared: Dictionary = {}
|
|
|
92
77
|
## declares, or a bare array positional for a vector. Each is its own function; asking all three
|
|
93
78
|
## in one was twenty-four exits deep and impossible to follow.
|
|
94
79
|
##
|
|
95
|
-
## [param elements] is what a typed Array or Dictionary holds, as [method elements_of]
|
|
80
|
+
## [param elements] is what a typed Array or Dictionary holds, as [method GdhDeclared.elements_of]
|
|
81
|
+
## reads it.
|
|
96
82
|
func parse(value: Variant, expected_type: int = TYPE_NIL, elements: Array = []) -> Variant:
|
|
97
83
|
if value is Dictionary:
|
|
98
84
|
var fields: Dictionary = value
|
|
@@ -189,51 +175,117 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
189
175
|
named[str(key).get_slice(":", 0)] = true
|
|
190
176
|
var before: Dictionary = _stored(target, named)
|
|
191
177
|
var given: Dictionary = {}
|
|
178
|
+
var declarations: Dictionary = {}
|
|
192
179
|
# A refusal of one of several leaves the others unwritten too, which a refusal naming only the
|
|
193
180
|
# one it refused left a caller to guess.
|
|
194
181
|
var unwritten: String = (
|
|
195
182
|
"" if properties.size() < 2 else " None of the %d properties given was written." % properties.size()
|
|
196
183
|
)
|
|
197
|
-
|
|
184
|
+
# The script first, wherever the caller put it, since the properties it declares exist only once
|
|
185
|
+
# it is attached.
|
|
186
|
+
var order: Array = properties.keys()
|
|
187
|
+
if order.has("script"):
|
|
188
|
+
order.erase("script")
|
|
189
|
+
order.push_front("script")
|
|
190
|
+
var home: String = GdhPropertyPaths.home_of(target)
|
|
191
|
+
for key: Variant in order:
|
|
198
192
|
var property: String = str(key)
|
|
199
|
-
var
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
var place: Array = GdhPropertyPaths.holder_of(target, property, home)
|
|
194
|
+
var holder: Object = place[0]
|
|
195
|
+
var name: String = place[1]
|
|
196
|
+
var declared: Dictionary = declaration(holder, name)
|
|
197
|
+
if declared.is_empty():
|
|
198
|
+
var elsewhere: String = GdhPropertyPaths.held_elsewhere(holder, name, home)
|
|
199
|
+
if not elsewhere.is_empty():
|
|
200
|
+
return elsewhere + unwritten
|
|
201
|
+
var ungrown: String = GdhPropertyPaths.grow(holder, name, GdhPropertyPaths.class_called(holder))
|
|
202
|
+
if not ungrown.is_empty():
|
|
203
|
+
return ungrown + unwritten
|
|
204
|
+
declared = declaration(holder, name)
|
|
205
|
+
if declared.is_empty() and ":" in name:
|
|
206
|
+
declared = _component_slot(holder, name)
|
|
202
207
|
if declared.has("refused"):
|
|
203
208
|
return str(declared["refused"]) + unwritten
|
|
204
|
-
if declared.is_empty() and
|
|
205
|
-
return _no_uniform(
|
|
209
|
+
if declared.is_empty() and holder is ShaderMaterial and name.begins_with("shader_parameter/"):
|
|
210
|
+
return _no_uniform(holder as ShaderMaterial, name) + unwritten
|
|
206
211
|
if declared.is_empty():
|
|
207
|
-
return
|
|
208
|
-
var value: Array = value_for(
|
|
212
|
+
return no_property(holder, name) + unwritten
|
|
213
|
+
var value: Array = value_for(holder, declared, properties[key])
|
|
209
214
|
if not value[0]:
|
|
210
215
|
return str(value[1]) + unwritten
|
|
211
|
-
var outside: String = GdhDeclared.outside(
|
|
216
|
+
var outside: String = GdhDeclared.outside(holder, declared, value[1], shown(value[1]))
|
|
212
217
|
if not outside.is_empty():
|
|
213
218
|
_outside_declared[property] = outside
|
|
214
219
|
_write(target, property, value[1])
|
|
215
220
|
given[property] = value[1]
|
|
221
|
+
declarations[property] = declared
|
|
216
222
|
|
|
217
223
|
for property: String in given:
|
|
218
|
-
if not same(read_at(target, property), given[property]):
|
|
224
|
+
if not GdhSame.same(read_at(target, property), given[property]):
|
|
219
225
|
_write(target, property, given[property])
|
|
220
226
|
for property: String in given:
|
|
221
227
|
var held: Variant = read_at(target, property)
|
|
222
|
-
if not same(held, given[property]):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"%s.%s was given %s and holds %s after it was set: the engine changed or refused the "
|
|
226
|
-
+ "value, so nothing was written."
|
|
227
|
-
)
|
|
228
|
-
% [target.get_class(), property, shown(given[property]), shown(held)]
|
|
229
|
-
)
|
|
228
|
+
if not GdhSame.same(held, given[property]):
|
|
229
|
+
var declared: Dictionary = declarations[property]
|
|
230
|
+
return _not_held(target, property, given[property], held, declared)
|
|
230
231
|
for property: String in before:
|
|
231
232
|
var now: Variant = target.get(property)
|
|
232
|
-
if not same(now, before[property]):
|
|
233
|
+
if not GdhSame.same(now, before[property]):
|
|
233
234
|
_also_changed[property] = [before[property], now]
|
|
234
235
|
return ""
|
|
235
236
|
|
|
236
237
|
|
|
238
|
+
## Why [param property] of [param target], declared as [param declared], holds [param held] after
|
|
239
|
+
## it was given [param value]. A text property with a list of names holds only one of them, and its
|
|
240
|
+
## getter answers its default for any other. A player's bus is one, though the engine fills its list
|
|
241
|
+
## only in the inspector, so the buses are read from the AudioServer, which holds the project's
|
|
242
|
+
## layout; "the engine changed or refused the value" left a caller to guess the bus did not exist.
|
|
243
|
+
func _not_held(
|
|
244
|
+
target: Object, property: String, value: Variant, held: Variant, declared: Dictionary
|
|
245
|
+
) -> String:
|
|
246
|
+
var said: String = (
|
|
247
|
+
"%s.%s was given %s and holds %s after it was set"
|
|
248
|
+
% [GdhPropertyPaths.class_called(target), property, shown(value), shown(held)]
|
|
249
|
+
)
|
|
250
|
+
var type: int = GdhRead.as_int(declared.get("type", TYPE_NIL))
|
|
251
|
+
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
252
|
+
var names: PackedStringArray = []
|
|
253
|
+
if hint == PROPERTY_HINT_ENUM and (type == TYPE_STRING or type == TYPE_STRING_NAME):
|
|
254
|
+
names = PackedStringArray(GdhDeclared.enum_values(str(declared.get("hint_string", ""))).keys())
|
|
255
|
+
var bus: bool = property == "bus" and target.get_class().begins_with("AudioStreamPlayer")
|
|
256
|
+
if bus:
|
|
257
|
+
names = []
|
|
258
|
+
for index: int in AudioServer.bus_count:
|
|
259
|
+
var _added: bool = names.append(AudioServer.get_bus_name(index))
|
|
260
|
+
if names.is_empty() or names.has(str(value)):
|
|
261
|
+
return said + ": the engine changed or refused the value, so nothing was written."
|
|
262
|
+
var close: PackedStringArray = GdhRead.nearest(str(value), names)
|
|
263
|
+
var nearest: String = (
|
|
264
|
+
"%s %s" % ["is" if close.size() == 1 else "are", ", ".join(close)] if not close.is_empty() else ""
|
|
265
|
+
)
|
|
266
|
+
if bus:
|
|
267
|
+
return (
|
|
268
|
+
(
|
|
269
|
+
"%s: it takes the name of one of the project's audio buses, and %s is none of them; %s. "
|
|
270
|
+
+ "project_info include audio_buses lists them, and project_settings add_audio_bus adds "
|
|
271
|
+
+ "one. Nothing was written."
|
|
272
|
+
)
|
|
273
|
+
% [
|
|
274
|
+
said,
|
|
275
|
+
shown(value),
|
|
276
|
+
"the buses are " + ", ".join(names) if nearest.is_empty() else "the nearest " + nearest
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
return (
|
|
280
|
+
"%s: it takes one of the names it lists, and %s is none of them; %s. Nothing was written."
|
|
281
|
+
% [
|
|
282
|
+
said,
|
|
283
|
+
shown(value),
|
|
284
|
+
"it lists " + ", ".join(names) if nearest.is_empty() else "the nearest it lists " + nearest
|
|
285
|
+
]
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
237
289
|
## Why [param material] has no [param property]: a ShaderMaterial's parameters are its shader's
|
|
238
290
|
## uniforms, so the shader is what lacks one, and "ShaderMaterial has no property" named the
|
|
239
291
|
## material.
|
|
@@ -306,18 +358,24 @@ func _stored(target: Object, leave: Dictionary) -> Dictionary:
|
|
|
306
358
|
|
|
307
359
|
## What [param target] holds under [param key], a property or a colon path into one's components.
|
|
308
360
|
func read_at(target: Object, key: String) -> Variant:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
361
|
+
var place: Array = GdhPropertyPaths.holder_of(target, key)
|
|
362
|
+
var holder: Object = place[0]
|
|
363
|
+
var name: String = place[1]
|
|
364
|
+
if ":" in name and declaration(holder, name).is_empty():
|
|
365
|
+
return holder.get_indexed(NodePath(name))
|
|
366
|
+
return holder.get(name)
|
|
312
367
|
|
|
313
368
|
|
|
314
369
|
## Writes [param value] under [param key], putting a changed component back into the property it
|
|
315
370
|
## was read out of, and that one into the one above it.
|
|
316
371
|
func _write(target: Object, key: String, value: Variant) -> void:
|
|
317
|
-
|
|
318
|
-
|
|
372
|
+
var place: Array = GdhPropertyPaths.holder_of(target, key)
|
|
373
|
+
var holder: Object = place[0]
|
|
374
|
+
var name: String = place[1]
|
|
375
|
+
if ":" in name and declaration(holder, name).is_empty():
|
|
376
|
+
holder.set_indexed(NodePath(name), value)
|
|
319
377
|
else:
|
|
320
|
-
|
|
378
|
+
holder.set(name, value)
|
|
321
379
|
|
|
322
380
|
|
|
323
381
|
## The declaration the last step of [param path] would have, typed as the component it reaches, so
|
|
@@ -325,9 +383,9 @@ func _write(target: Object, key: String, value: Variant) -> void:
|
|
|
325
383
|
## {"refused": why} for the first step that is not there.
|
|
326
384
|
func _component_slot(target: Object, path: String) -> Dictionary:
|
|
327
385
|
var steps: PackedStringArray = path.split(":")
|
|
328
|
-
var owner: String =
|
|
386
|
+
var owner: String = GdhPropertyPaths.class_called(target)
|
|
329
387
|
if declaration(target, steps[0]).is_empty():
|
|
330
|
-
return {"refused":
|
|
388
|
+
return {"refused": no_property(target, steps[0])}
|
|
331
389
|
var value: Variant = target.get(steps[0])
|
|
332
390
|
var walked: String = steps[0]
|
|
333
391
|
for index: int in range(1, steps.size()):
|
|
@@ -348,7 +406,7 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
348
406
|
if not step in has:
|
|
349
407
|
return {
|
|
350
408
|
"refused":
|
|
351
|
-
"%s.%s is %s, so it has no %s; it has %s" % [owner, walked, sort, step, ", ".join(has)]
|
|
409
|
+
"%s.%s is %s, so it has no %s; it has %s." % [owner, walked, sort, step, ", ".join(has)]
|
|
352
410
|
}
|
|
353
411
|
value = value[step]
|
|
354
412
|
walked += ":" + step
|
|
@@ -370,10 +428,17 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
370
428
|
if chosen == null:
|
|
371
429
|
var hinted: Dictionary = GdhDeclared.enum_values(str(declared.get("hint_string", "")))
|
|
372
430
|
chosen = GdhDeclared.enum_named(hinted, str(raw))
|
|
431
|
+
var constants: Dictionary = {}
|
|
432
|
+
if chosen == null:
|
|
433
|
+
constants = GdhDeclared.engine_constants(target, property)
|
|
434
|
+
chosen = GdhDeclared.enum_named(constants, str(raw))
|
|
373
435
|
if chosen != null:
|
|
374
436
|
return [true, chosen]
|
|
375
437
|
if not str(raw).strip_edges().is_valid_int():
|
|
376
|
-
|
|
438
|
+
var why: String = GdhDeclared.enum_refusal(
|
|
439
|
+
GdhPropertyPaths.class_called(target), property, str(raw), choices, constants
|
|
440
|
+
)
|
|
441
|
+
return [false, why]
|
|
377
442
|
|
|
378
443
|
# A resource-valued property takes the path of one, which is how a caller names a TileSet,
|
|
379
444
|
# a material or a theme: there is no other way to hand a tool a Resource.
|
|
@@ -384,17 +449,28 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
384
449
|
return found
|
|
385
450
|
value = found[1]
|
|
386
451
|
else:
|
|
387
|
-
var elements: Array = elements_of(declared)
|
|
452
|
+
var elements: Array = GdhDeclared.elements_of(declared)
|
|
388
453
|
if _fraction(raw, expected_type, elements):
|
|
389
454
|
return [false, _fraction_refusal(target, property, raw)]
|
|
390
455
|
var rounded: String = GdhSerialisation.inexact(raw, expected_type)
|
|
391
456
|
if not rounded.is_empty():
|
|
392
|
-
return [
|
|
457
|
+
return [
|
|
458
|
+
false, "%s.%s holds int: %s." % [GdhPropertyPaths.class_called(target), property, rounded]
|
|
459
|
+
]
|
|
393
460
|
_refused = ""
|
|
394
461
|
value = parse(raw, expected_type, elements)
|
|
395
462
|
if not _refused.is_empty():
|
|
396
463
|
return [false, _refused]
|
|
397
|
-
|
|
464
|
+
if expected_type == TYPE_ARRAY and elements == [TYPE_OBJECT] and value is Array:
|
|
465
|
+
var listed: Array = value
|
|
466
|
+
return _object_list(target, declared, listed)
|
|
467
|
+
if expected_type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
468
|
+
if GdhRead.as_int(elements[1], TYPE_NIL) == TYPE_OBJECT:
|
|
469
|
+
var mapped: Dictionary = value
|
|
470
|
+
return _object_entries(target, declared, mapped)
|
|
471
|
+
var refusal: String = cannot_hold(
|
|
472
|
+
GdhPropertyPaths.class_called(target), property, value, expected_type, elements
|
|
473
|
+
)
|
|
398
474
|
if not refusal.is_empty():
|
|
399
475
|
return [false, refusal]
|
|
400
476
|
if value is Object:
|
|
@@ -449,60 +525,102 @@ func _wrong_class(value: Object, declared: Dictionary) -> String:
|
|
|
449
525
|
return ""
|
|
450
526
|
var names: PackedStringArray = wanted.split(",", false)
|
|
451
527
|
for name: String in names:
|
|
452
|
-
if
|
|
528
|
+
if GdhDeclared.is_a(value, name.strip_edges()):
|
|
453
529
|
return ""
|
|
454
|
-
var script: Script = value.get_script()
|
|
455
|
-
while script != null:
|
|
456
|
-
if str(script.get_global_name()) == name.strip_edges():
|
|
457
|
-
return ""
|
|
458
|
-
script = script.get_base_script()
|
|
459
530
|
return (
|
|
460
531
|
"%s holds %s, and the value given is a %s."
|
|
461
532
|
% [str(declared.get("name", "")), " or ".join(names), value.get_class()]
|
|
462
533
|
)
|
|
463
534
|
|
|
464
535
|
|
|
465
|
-
##
|
|
466
|
-
## [
|
|
467
|
-
##
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
var
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
var
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
536
|
+
## [param value] as the typed list of objects that [param declared] holds on [param target], as
|
|
537
|
+
## [ok, list or why not]. Measured on 4.7.2, a scene's node whose script exports Array[Item] holds []
|
|
538
|
+
## after being given an untyped list of Items, and keeps a list typed as its own is.
|
|
539
|
+
func _object_list(target: Object, declared: Dictionary, value: Array) -> Array:
|
|
540
|
+
var property: String = str(declared.get("name", ""))
|
|
541
|
+
var kind: Array = GdhDeclared.object_kind(declared, 0)
|
|
542
|
+
var holding: String = "%s.%s holds %s" % [GdhPropertyPaths.class_called(target), property, kind[0]]
|
|
543
|
+
var items: Array = []
|
|
544
|
+
for index: int in value.size():
|
|
545
|
+
var place: Array = ["item %d of the list given" % index, "item %d of %s" % [index, property]]
|
|
546
|
+
var item: Array = _object_item(target, value[index], kind, holding, place)
|
|
547
|
+
if not item[0]:
|
|
548
|
+
return item
|
|
549
|
+
items.append(item[1])
|
|
550
|
+
var held: Variant = target.get(property)
|
|
551
|
+
var current: Array = held if held is Array else []
|
|
552
|
+
if current.is_typed():
|
|
553
|
+
return [
|
|
554
|
+
true,
|
|
555
|
+
Array(
|
|
556
|
+
items, current.get_typed_builtin(), current.get_typed_class_name(), current.get_typed_script()
|
|
557
|
+
)
|
|
558
|
+
]
|
|
559
|
+
var typing: Array = GdhDeclared.object_typing(str(kind[0]))
|
|
560
|
+
if typing.is_empty():
|
|
561
|
+
return [true, items]
|
|
562
|
+
var base: StringName = typing[0]
|
|
563
|
+
var script: Script = typing[1]
|
|
564
|
+
return [true, Array(items, TYPE_OBJECT, base, script)]
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
## [param value] as the typed map to objects that [param declared] holds on [param target], as
|
|
568
|
+
## [ok, map or why not], typed for the same reason as [method _object_list].
|
|
569
|
+
func _object_entries(target: Object, declared: Dictionary, value: Dictionary) -> Array:
|
|
570
|
+
var property: String = str(declared.get("name", ""))
|
|
571
|
+
var kind: Array = GdhDeclared.object_kind(declared, 1)
|
|
572
|
+
var holding: String = "%s.%s holds %s values" % [GdhPropertyPaths.class_called(target), property, kind[0]]
|
|
573
|
+
var entries: Dictionary = {}
|
|
574
|
+
for key: Variant in value:
|
|
575
|
+
var place: Array = ["the one given for %s" % shown(key), "%s[%s]" % [property, shown(key)]]
|
|
576
|
+
var entry: Array = _object_item(target, value[key], kind, holding, place)
|
|
577
|
+
if not entry[0]:
|
|
578
|
+
return entry
|
|
579
|
+
entries[key] = entry[1]
|
|
580
|
+
var held: Variant = target.get(property)
|
|
581
|
+
var current: Dictionary = held if held is Dictionary else {}
|
|
582
|
+
if current.is_typed():
|
|
583
|
+
return [
|
|
584
|
+
true,
|
|
585
|
+
Dictionary(
|
|
586
|
+
entries,
|
|
587
|
+
current.get_typed_key_builtin(),
|
|
588
|
+
current.get_typed_key_class_name(),
|
|
589
|
+
current.get_typed_key_script(),
|
|
590
|
+
current.get_typed_value_builtin(),
|
|
591
|
+
current.get_typed_value_class_name(),
|
|
592
|
+
current.get_typed_value_script()
|
|
593
|
+
)
|
|
594
|
+
]
|
|
595
|
+
var typing: Array = GdhDeclared.object_typing(str(kind[0]))
|
|
596
|
+
var key_type: int = GdhRead.as_int(GdhDeclared.elements_of(declared)[0], TYPE_NIL)
|
|
597
|
+
if typing.is_empty() or key_type == TYPE_OBJECT:
|
|
598
|
+
return [true, entries]
|
|
599
|
+
var base: StringName = typing[0]
|
|
600
|
+
var script: Script = typing[1]
|
|
601
|
+
return [true, Dictionary(entries, key_type, &"", null, TYPE_OBJECT, base, script)]
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
## [param given], an object for a typed list or map, as [ok, object or why not]: a resource's
|
|
605
|
+
## res:// path is the resource, and for nodes a path from [param target] is the node there.
|
|
606
|
+
## [param holding] says what the list or map holds, and [param place] is [where the value sits in
|
|
607
|
+
## what was given, what it is called in the property].
|
|
608
|
+
func _object_item(target: Object, given: Variant, kind: Array, holding: String, place: Array) -> Array:
|
|
609
|
+
var item: Variant = given
|
|
610
|
+
if typeof(item) == TYPE_STRING:
|
|
611
|
+
var found: Array = (
|
|
612
|
+
_node_reference(target, str(place[1]), item)
|
|
613
|
+
if kind[1]
|
|
614
|
+
else _resource_at(str(item), str(place[1]))
|
|
615
|
+
)
|
|
616
|
+
if not found[0]:
|
|
617
|
+
return found
|
|
618
|
+
item = found[1]
|
|
619
|
+
var object: Object = item if item is Object else null
|
|
620
|
+
if item == null or (object != null and GdhDeclared.is_a(object, str(kind[0]))):
|
|
621
|
+
return [true, item]
|
|
622
|
+
var named: String = object.get_class() if object != null else shown(item)
|
|
623
|
+
return [false, "%s, and %s is %s." % [holding, place[0], named]]
|
|
506
624
|
|
|
507
625
|
|
|
508
626
|
## Whether [param value] holds a number with a fractional part where an integer is wanted, in a
|
|
@@ -539,7 +657,7 @@ func _fraction_refusal(target: Object, property: String, raw: Variant) -> String
|
|
|
539
657
|
"%s.%s holds whole numbers, and %s would be cut to fit. Setting it would save a different "
|
|
540
658
|
+ "value than the one asked for."
|
|
541
659
|
)
|
|
542
|
-
% [class_called(target), property, shown(raw)]
|
|
660
|
+
% [GdhPropertyPaths.class_called(target), property, shown(raw)]
|
|
543
661
|
)
|
|
544
662
|
|
|
545
663
|
|
|
@@ -564,16 +682,15 @@ func colour_from(raw: Variant, named: String) -> Array:
|
|
|
564
682
|
]
|
|
565
683
|
|
|
566
684
|
|
|
567
|
-
##
|
|
568
|
-
##
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return target.get_class()
|
|
685
|
+
## The refusal for [param property], which [param target] does not declare, naming the declared
|
|
686
|
+
## ones nearest it: "StyleBoxFlat has no property shadow_sizee" left the caller nothing to send.
|
|
687
|
+
static func no_property(target: Object, property: String) -> String:
|
|
688
|
+
return (
|
|
689
|
+
GdhRead.near_property(
|
|
690
|
+
"%s has no property %s" % [GdhPropertyPaths.class_called(target), property], target, property
|
|
691
|
+
)
|
|
692
|
+
+ "."
|
|
693
|
+
)
|
|
577
694
|
|
|
578
695
|
|
|
579
696
|
## The resource [param path] names, as [found, resource or why not].
|
|
@@ -621,15 +738,10 @@ func _resource_at(path: String, property: String) -> Array:
|
|
|
621
738
|
## its name, so without this a typo or a heading saved a file that had not changed and reported it
|
|
622
739
|
## as one that had.
|
|
623
740
|
func declaration(target: Object, property: String) -> Dictionary:
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
continue
|
|
627
|
-
if GdhRead.as_int(declared.get("usage", 0)) & HEADINGS:
|
|
628
|
-
continue
|
|
629
|
-
return declared
|
|
630
|
-
if target is Theme:
|
|
741
|
+
var declared: Dictionary = GdhPropertyPaths.declared_on(target, property)
|
|
742
|
+
if declared.is_empty() and target is Theme:
|
|
631
743
|
return _theme_item(property)
|
|
632
|
-
return
|
|
744
|
+
return declared
|
|
633
745
|
|
|
634
746
|
|
|
635
747
|
## The entry a Theme would declare for [param property] once it held that item, or {} when the name
|
|
@@ -648,107 +760,6 @@ static func _theme_item(property: String) -> Dictionary:
|
|
|
648
760
|
return made
|
|
649
761
|
|
|
650
762
|
|
|
651
|
-
## Whether [param held] is [param given], as a property read back after a set answers it.
|
|
652
|
-
##
|
|
653
|
-
## Close enough rather than equal for anything made of floats: a Vector2 property stores 32-bit
|
|
654
|
-
## components, so 0.1 comes back as 0.100000001. A packed array read back against the list it was
|
|
655
|
-
## given compares element by element, since `==` between the two is an error rather than false.
|
|
656
|
-
static func same(held: Variant, given: Variant) -> bool:
|
|
657
|
-
var held_type: int = typeof(held)
|
|
658
|
-
var given_type: int = typeof(given)
|
|
659
|
-
# An empty object property reads back as an Object that is null, not as nil, and the two are
|
|
660
|
-
# the same nothing.
|
|
661
|
-
var held_none: bool = held_type == TYPE_NIL or (held_type == TYPE_OBJECT and held == null)
|
|
662
|
-
var given_none: bool = given_type == TYPE_NIL or (given_type == TYPE_OBJECT and given == null)
|
|
663
|
-
if held_none or given_none:
|
|
664
|
-
return held_none == given_none
|
|
665
|
-
if held_type == TYPE_OBJECT or given_type == TYPE_OBJECT:
|
|
666
|
-
return _same_object(held, given)
|
|
667
|
-
if GdhSerialisation.NUMBERS.has(held_type) and GdhSerialisation.NUMBERS.has(given_type):
|
|
668
|
-
if held_type == TYPE_BOOL or given_type == TYPE_BOOL:
|
|
669
|
-
return held_type == given_type and held == given
|
|
670
|
-
var held_number: float = GdhRead.as_float(held)
|
|
671
|
-
var given_number: float = GdhRead.as_float(given)
|
|
672
|
-
return (is_nan(held_number) and is_nan(given_number)) or is_equal_approx(held_number, given_number)
|
|
673
|
-
if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
|
|
674
|
-
return str(held) == str(given)
|
|
675
|
-
if _listed(held_type) and _listed(given_type):
|
|
676
|
-
var held_items: Array = type_convert(held, TYPE_ARRAY)
|
|
677
|
-
var given_items: Array = type_convert(given, TYPE_ARRAY)
|
|
678
|
-
if held_items.size() != given_items.size():
|
|
679
|
-
return false
|
|
680
|
-
for index: int in held_items.size():
|
|
681
|
-
if not same(held_items[index], given_items[index]):
|
|
682
|
-
return false
|
|
683
|
-
return true
|
|
684
|
-
if held_type == TYPE_DICTIONARY and given_type == TYPE_DICTIONARY:
|
|
685
|
-
var held_entries: Dictionary = held
|
|
686
|
-
var given_entries: Dictionary = given
|
|
687
|
-
return _same_entries(held_entries, given_entries)
|
|
688
|
-
if held_type != given_type:
|
|
689
|
-
return false
|
|
690
|
-
return _same_shape(held, given)
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
static func _same_object(held: Variant, given: Variant) -> bool:
|
|
694
|
-
if typeof(held) != TYPE_OBJECT or typeof(given) != TYPE_OBJECT:
|
|
695
|
-
return false
|
|
696
|
-
if is_same(held, given):
|
|
697
|
-
return true
|
|
698
|
-
if not held is Resource or not given is Resource:
|
|
699
|
-
return false
|
|
700
|
-
var held_resource: Resource = held
|
|
701
|
-
var given_resource: Resource = given
|
|
702
|
-
if not held_resource.resource_path.is_empty():
|
|
703
|
-
return held_resource.resource_path == given_resource.resource_path
|
|
704
|
-
# A resource local to the scene is duplicated for every instance, so the copy read back is
|
|
705
|
-
# another object of the same class.
|
|
706
|
-
return held_resource.resource_local_to_scene and held_resource.get_class() == given_resource.get_class()
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
static func _same_entries(held: Dictionary, given: Dictionary) -> bool:
|
|
710
|
-
if held.size() != given.size():
|
|
711
|
-
return false
|
|
712
|
-
for key: Variant in given:
|
|
713
|
-
var found: bool = false
|
|
714
|
-
for held_key: Variant in held:
|
|
715
|
-
if same(held_key, key):
|
|
716
|
-
found = same(held[held_key], given[key])
|
|
717
|
-
break
|
|
718
|
-
if not found:
|
|
719
|
-
return false
|
|
720
|
-
return true
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
## Two values of one type made of floats, compared component by component through the type's own
|
|
724
|
-
## text form, which lists every component: each type has its own is_equal_approx, and reaching it
|
|
725
|
-
## through a Variant is a call the typed gate refuses.
|
|
726
|
-
static func _same_shape(held: Variant, given: Variant) -> bool:
|
|
727
|
-
if typeof(held) not in FLOAT_SHAPES:
|
|
728
|
-
return held == given
|
|
729
|
-
var held_parts: Array[float] = _components(held)
|
|
730
|
-
var given_parts: Array[float] = _components(given)
|
|
731
|
-
if held_parts.size() != given_parts.size():
|
|
732
|
-
return false
|
|
733
|
-
for index: int in held_parts.size():
|
|
734
|
-
if not same(held_parts[index], given_parts[index]):
|
|
735
|
-
return false
|
|
736
|
-
return true
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
static func _components(value: Variant) -> Array[float]:
|
|
740
|
-
var parts: Array[float] = []
|
|
741
|
-
var text: String = var_to_str(value)
|
|
742
|
-
var inner: String = text.substr(text.find("(") + 1).trim_suffix(")")
|
|
743
|
-
for part: String in inner.split(",", false):
|
|
744
|
-
parts.append(part.strip_edges().to_float())
|
|
745
|
-
return parts
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
static func _listed(type: int) -> bool:
|
|
749
|
-
return type == TYPE_ARRAY or PACKED_ELEMENTS.has(type)
|
|
750
|
-
|
|
751
|
-
|
|
752
763
|
## [param value] as a caller would write it, for a refusal to quote.
|
|
753
764
|
func shown(value: Variant) -> String:
|
|
754
765
|
return JSON.stringify(_values.serialize_value(value), "", false)
|
|
@@ -785,18 +796,22 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
785
796
|
_refused = str(found[1])
|
|
786
797
|
return [true, null]
|
|
787
798
|
return [true, found[1]]
|
|
788
|
-
# What a read answers
|
|
799
|
+
# What a read answers where a resource built into a scene recurs inside itself: a class and no
|
|
789
800
|
# path. Taken as nothing, it cleared the property it was written back to.
|
|
790
801
|
if value.has("class"):
|
|
791
802
|
_refused = (
|
|
792
803
|
(
|
|
793
|
-
"The %s given has no path:
|
|
794
|
-
+
|
|
795
|
-
+ "properties beside it.
|
|
804
|
+
"The %s given has no path: a read names a resource built into a scene by its class "
|
|
805
|
+
+ "alone where it recurs inside itself, and nothing elsewhere can refer to it. Build "
|
|
806
|
+
+ 'a new one with "_type" set to its class and its properties beside it.'
|
|
796
807
|
)
|
|
797
808
|
% str(value["class"])
|
|
798
809
|
)
|
|
799
810
|
return [true, null]
|
|
811
|
+
# A plain Resource with a script or values of its own, as a read answers one built into a scene,
|
|
812
|
+
# is one to build; taken as nothing, writing such a read back cleared the property.
|
|
813
|
+
if value.size() > 1:
|
|
814
|
+
return _parse_new_resource("Resource", value)
|
|
800
815
|
return [true, null]
|
|
801
816
|
|
|
802
817
|
|
|
@@ -804,15 +819,21 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
804
819
|
## NavigationRegion2D can arrive with its NavigationPolygon and an AnimationTree with its root
|
|
805
820
|
## state machine in the same add as any other property.
|
|
806
821
|
func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
822
|
+
var built: Resource = null
|
|
807
823
|
if (
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
or not ClassDB.can_instantiate(type_tag)
|
|
824
|
+
ClassDB.class_exists(type_tag)
|
|
825
|
+
and ClassDB.is_parent_class(type_tag, "Resource")
|
|
826
|
+
and ClassDB.can_instantiate(type_tag)
|
|
812
827
|
):
|
|
828
|
+
built = ClassDB.instantiate(type_tag)
|
|
829
|
+
elif not type_tag.is_empty() and not ClassDB.class_exists(type_tag):
|
|
830
|
+
# A script's class_name, as a read answers it under scriptClass.
|
|
831
|
+
var script: Script = GdhDeclared.script_class(type_tag)
|
|
832
|
+
if script != null and ClassDB.is_parent_class(script.get_instance_base_type(), "Resource"):
|
|
833
|
+
built = ClassDB.instantiate(script.get_instance_base_type())
|
|
834
|
+
built.set_script(script)
|
|
835
|
+
if built == null:
|
|
813
836
|
return [false, null]
|
|
814
|
-
|
|
815
|
-
var built: Resource = ClassDB.instantiate(type_tag)
|
|
816
837
|
var wanted: Dictionary = {}
|
|
817
838
|
for key: Variant in value:
|
|
818
839
|
var property: String = str(key)
|
|
@@ -822,6 +843,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
822
843
|
if not refused.is_empty():
|
|
823
844
|
_refused = refused
|
|
824
845
|
return [true, null]
|
|
846
|
+
GdhDeclared.unmade(built, wanted)
|
|
825
847
|
return [true, built]
|
|
826
848
|
|
|
827
849
|
|