gdharness 1.3.3 → 1.3.5
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 +532 -456
- package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
- package/build/godot/addons/gdharness_editor/declared_values.gd +181 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
- 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 +192 -231
- 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 +97 -14
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
- package/build/godot/addons/gdharness_editor/tool_executor.gd +9 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +38 -35
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +20 -12
- 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 +68 -2
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +94 -333
- 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/declared_values.gd +293 -0
- package/build/godot/addons/gdharness_runtime/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +26 -39
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +18 -0
- 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 +62 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +48 -15
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +3 -2
- package/build/godot/operations/classdb_queries.gd +112 -46
- package/build/godot/operations/declared_values.gd +181 -4
- package/build/godot/operations/dependencies.gd +14 -39
- package/build/godot/operations/editor_tools.gd +4 -3
- package/build/godot/operations/file_reads.gd +15 -3
- package/build/godot/operations/gdscript_analysis.gd +229 -75
- package/build/godot/operations/gdscript_authoring.gd +157 -36
- 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 +192 -231
- 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/build/keeper.js +83 -69
- 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
|
|
@@ -195,21 +181,39 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
195
181
|
var unwritten: String = (
|
|
196
182
|
"" if properties.size() < 2 else " None of the %d properties given was written." % properties.size()
|
|
197
183
|
)
|
|
198
|
-
|
|
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:
|
|
199
192
|
var property: String = str(key)
|
|
200
|
-
var
|
|
201
|
-
|
|
202
|
-
|
|
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)
|
|
203
207
|
if declared.has("refused"):
|
|
204
208
|
return str(declared["refused"]) + unwritten
|
|
205
|
-
if declared.is_empty() and
|
|
206
|
-
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
|
|
207
211
|
if declared.is_empty():
|
|
208
|
-
return
|
|
209
|
-
var value: Array = value_for(
|
|
212
|
+
return no_property(holder, name) + unwritten
|
|
213
|
+
var value: Array = value_for(holder, declared, properties[key])
|
|
210
214
|
if not value[0]:
|
|
211
215
|
return str(value[1]) + unwritten
|
|
212
|
-
var outside: String = GdhDeclared.outside(
|
|
216
|
+
var outside: String = GdhDeclared.outside(holder, declared, value[1], shown(value[1]))
|
|
213
217
|
if not outside.is_empty():
|
|
214
218
|
_outside_declared[property] = outside
|
|
215
219
|
_write(target, property, value[1])
|
|
@@ -217,16 +221,16 @@ func write_all(target: Object, properties: Dictionary) -> String:
|
|
|
217
221
|
declarations[property] = declared
|
|
218
222
|
|
|
219
223
|
for property: String in given:
|
|
220
|
-
if not same(read_at(target, property), given[property]):
|
|
224
|
+
if not GdhSame.same(read_at(target, property), given[property]):
|
|
221
225
|
_write(target, property, given[property])
|
|
222
226
|
for property: String in given:
|
|
223
227
|
var held: Variant = read_at(target, property)
|
|
224
|
-
if not same(held, given[property]):
|
|
228
|
+
if not GdhSame.same(held, given[property]):
|
|
225
229
|
var declared: Dictionary = declarations[property]
|
|
226
230
|
return _not_held(target, property, given[property], held, declared)
|
|
227
231
|
for property: String in before:
|
|
228
232
|
var now: Variant = target.get(property)
|
|
229
|
-
if not same(now, before[property]):
|
|
233
|
+
if not GdhSame.same(now, before[property]):
|
|
230
234
|
_also_changed[property] = [before[property], now]
|
|
231
235
|
return ""
|
|
232
236
|
|
|
@@ -241,7 +245,7 @@ func _not_held(
|
|
|
241
245
|
) -> String:
|
|
242
246
|
var said: String = (
|
|
243
247
|
"%s.%s was given %s and holds %s after it was set"
|
|
244
|
-
% [
|
|
248
|
+
% [GdhPropertyPaths.class_called(target), property, shown(value), shown(held)]
|
|
245
249
|
)
|
|
246
250
|
var type: int = GdhRead.as_int(declared.get("type", TYPE_NIL))
|
|
247
251
|
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
@@ -354,18 +358,24 @@ func _stored(target: Object, leave: Dictionary) -> Dictionary:
|
|
|
354
358
|
|
|
355
359
|
## What [param target] holds under [param key], a property or a colon path into one's components.
|
|
356
360
|
func read_at(target: Object, key: String) -> Variant:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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)
|
|
360
367
|
|
|
361
368
|
|
|
362
369
|
## Writes [param value] under [param key], putting a changed component back into the property it
|
|
363
370
|
## was read out of, and that one into the one above it.
|
|
364
371
|
func _write(target: Object, key: String, value: Variant) -> void:
|
|
365
|
-
|
|
366
|
-
|
|
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)
|
|
367
377
|
else:
|
|
368
|
-
|
|
378
|
+
holder.set(name, value)
|
|
369
379
|
|
|
370
380
|
|
|
371
381
|
## The declaration the last step of [param path] would have, typed as the component it reaches, so
|
|
@@ -373,9 +383,9 @@ func _write(target: Object, key: String, value: Variant) -> void:
|
|
|
373
383
|
## {"refused": why} for the first step that is not there.
|
|
374
384
|
func _component_slot(target: Object, path: String) -> Dictionary:
|
|
375
385
|
var steps: PackedStringArray = path.split(":")
|
|
376
|
-
var owner: String =
|
|
386
|
+
var owner: String = GdhPropertyPaths.class_called(target)
|
|
377
387
|
if declaration(target, steps[0]).is_empty():
|
|
378
|
-
return {"refused":
|
|
388
|
+
return {"refused": no_property(target, steps[0])}
|
|
379
389
|
var value: Variant = target.get(steps[0])
|
|
380
390
|
var walked: String = steps[0]
|
|
381
391
|
for index: int in range(1, steps.size()):
|
|
@@ -396,7 +406,7 @@ func _component_slot(target: Object, path: String) -> Dictionary:
|
|
|
396
406
|
if not step in has:
|
|
397
407
|
return {
|
|
398
408
|
"refused":
|
|
399
|
-
"%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)]
|
|
400
410
|
}
|
|
401
411
|
value = value[step]
|
|
402
412
|
walked += ":" + step
|
|
@@ -410,18 +420,12 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
410
420
|
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
411
421
|
if expected_type == TYPE_OBJECT and hint == PROPERTY_HINT_NODE_TYPE:
|
|
412
422
|
return _node_reference(target, property, raw)
|
|
413
|
-
# An enum is written by the name of a value as well as its number, since the name is what the
|
|
414
|
-
# script and the inspector show and the number is only where the value happens to sit.
|
|
415
423
|
if expected_type == TYPE_INT and hint == PROPERTY_HINT_ENUM and typeof(raw) == TYPE_STRING:
|
|
416
|
-
var
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if chosen != null:
|
|
422
|
-
return [true, chosen]
|
|
423
|
-
if not str(raw).strip_edges().is_valid_int():
|
|
424
|
-
return [false, GdhDeclared.enum_refusal(class_called(target), property, str(raw), choices)]
|
|
424
|
+
var by_name: Array = GdhDeclared.enum_by_name(
|
|
425
|
+
target, declared, str(raw), GdhPropertyPaths.class_called(target)
|
|
426
|
+
)
|
|
427
|
+
if by_name[0] or not str(by_name[1]).is_empty():
|
|
428
|
+
return by_name
|
|
425
429
|
|
|
426
430
|
# A resource-valued property takes the path of one, which is how a caller names a TileSet,
|
|
427
431
|
# a material or a theme: there is no other way to hand a tool a Resource.
|
|
@@ -432,17 +436,28 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
432
436
|
return found
|
|
433
437
|
value = found[1]
|
|
434
438
|
else:
|
|
435
|
-
var elements: Array = elements_of(declared)
|
|
439
|
+
var elements: Array = GdhDeclared.elements_of(declared)
|
|
436
440
|
if _fraction(raw, expected_type, elements):
|
|
437
441
|
return [false, _fraction_refusal(target, property, raw)]
|
|
438
442
|
var rounded: String = GdhSerialisation.inexact(raw, expected_type)
|
|
439
443
|
if not rounded.is_empty():
|
|
440
|
-
return [
|
|
444
|
+
return [
|
|
445
|
+
false, "%s.%s holds int: %s." % [GdhPropertyPaths.class_called(target), property, rounded]
|
|
446
|
+
]
|
|
441
447
|
_refused = ""
|
|
442
448
|
value = parse(raw, expected_type, elements)
|
|
443
449
|
if not _refused.is_empty():
|
|
444
450
|
return [false, _refused]
|
|
445
|
-
|
|
451
|
+
if expected_type == TYPE_ARRAY and elements == [TYPE_OBJECT] and value is Array:
|
|
452
|
+
var listed: Array = value
|
|
453
|
+
return _object_list(target, declared, listed)
|
|
454
|
+
if expected_type == TYPE_DICTIONARY and elements.size() == 2 and value is Dictionary:
|
|
455
|
+
if GdhRead.as_int(elements[1], TYPE_NIL) == TYPE_OBJECT:
|
|
456
|
+
var mapped: Dictionary = value
|
|
457
|
+
return _object_entries(target, declared, mapped)
|
|
458
|
+
var refusal: String = cannot_hold(
|
|
459
|
+
GdhPropertyPaths.class_called(target), property, value, expected_type, elements
|
|
460
|
+
)
|
|
446
461
|
if not refusal.is_empty():
|
|
447
462
|
return [false, refusal]
|
|
448
463
|
if value is Object:
|
|
@@ -497,60 +512,102 @@ func _wrong_class(value: Object, declared: Dictionary) -> String:
|
|
|
497
512
|
return ""
|
|
498
513
|
var names: PackedStringArray = wanted.split(",", false)
|
|
499
514
|
for name: String in names:
|
|
500
|
-
if
|
|
515
|
+
if GdhDeclared.is_a(value, name.strip_edges()):
|
|
501
516
|
return ""
|
|
502
|
-
var script: Script = value.get_script()
|
|
503
|
-
while script != null:
|
|
504
|
-
if str(script.get_global_name()) == name.strip_edges():
|
|
505
|
-
return ""
|
|
506
|
-
script = script.get_base_script()
|
|
507
517
|
return (
|
|
508
518
|
"%s holds %s, and the value given is a %s."
|
|
509
519
|
% [str(declared.get("name", "")), " or ".join(names), value.get_class()]
|
|
510
520
|
)
|
|
511
521
|
|
|
512
522
|
|
|
513
|
-
##
|
|
514
|
-
## [
|
|
515
|
-
##
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
var
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
var
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
523
|
+
## [param value] as the typed list of objects that [param declared] holds on [param target], as
|
|
524
|
+
## [ok, list or why not]. Measured on 4.7.2, a scene's node whose script exports Array[Item] holds []
|
|
525
|
+
## after being given an untyped list of Items, and keeps a list typed as its own is.
|
|
526
|
+
func _object_list(target: Object, declared: Dictionary, value: Array) -> Array:
|
|
527
|
+
var property: String = str(declared.get("name", ""))
|
|
528
|
+
var kind: Array = GdhDeclared.object_kind(declared, 0)
|
|
529
|
+
var holding: String = "%s.%s holds %s" % [GdhPropertyPaths.class_called(target), property, kind[0]]
|
|
530
|
+
var items: Array = []
|
|
531
|
+
for index: int in value.size():
|
|
532
|
+
var place: Array = ["item %d of the list given" % index, "item %d of %s" % [index, property]]
|
|
533
|
+
var item: Array = _object_item(target, value[index], kind, holding, place)
|
|
534
|
+
if not item[0]:
|
|
535
|
+
return item
|
|
536
|
+
items.append(item[1])
|
|
537
|
+
var held: Variant = target.get(property)
|
|
538
|
+
var current: Array = held if held is Array else []
|
|
539
|
+
if current.is_typed():
|
|
540
|
+
return [
|
|
541
|
+
true,
|
|
542
|
+
Array(
|
|
543
|
+
items, current.get_typed_builtin(), current.get_typed_class_name(), current.get_typed_script()
|
|
544
|
+
)
|
|
545
|
+
]
|
|
546
|
+
var typing: Array = GdhDeclared.object_typing(str(kind[0]))
|
|
547
|
+
if typing.is_empty():
|
|
548
|
+
return [true, items]
|
|
549
|
+
var base: StringName = typing[0]
|
|
550
|
+
var script: Script = typing[1]
|
|
551
|
+
return [true, Array(items, TYPE_OBJECT, base, script)]
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
## [param value] as the typed map to objects that [param declared] holds on [param target], as
|
|
555
|
+
## [ok, map or why not], typed for the same reason as [method _object_list].
|
|
556
|
+
func _object_entries(target: Object, declared: Dictionary, value: Dictionary) -> Array:
|
|
557
|
+
var property: String = str(declared.get("name", ""))
|
|
558
|
+
var kind: Array = GdhDeclared.object_kind(declared, 1)
|
|
559
|
+
var holding: String = "%s.%s holds %s values" % [GdhPropertyPaths.class_called(target), property, kind[0]]
|
|
560
|
+
var entries: Dictionary = {}
|
|
561
|
+
for key: Variant in value:
|
|
562
|
+
var place: Array = ["the one given for %s" % shown(key), "%s[%s]" % [property, shown(key)]]
|
|
563
|
+
var entry: Array = _object_item(target, value[key], kind, holding, place)
|
|
564
|
+
if not entry[0]:
|
|
565
|
+
return entry
|
|
566
|
+
entries[key] = entry[1]
|
|
567
|
+
var held: Variant = target.get(property)
|
|
568
|
+
var current: Dictionary = held if held is Dictionary else {}
|
|
569
|
+
if current.is_typed():
|
|
570
|
+
return [
|
|
571
|
+
true,
|
|
572
|
+
Dictionary(
|
|
573
|
+
entries,
|
|
574
|
+
current.get_typed_key_builtin(),
|
|
575
|
+
current.get_typed_key_class_name(),
|
|
576
|
+
current.get_typed_key_script(),
|
|
577
|
+
current.get_typed_value_builtin(),
|
|
578
|
+
current.get_typed_value_class_name(),
|
|
579
|
+
current.get_typed_value_script()
|
|
580
|
+
)
|
|
581
|
+
]
|
|
582
|
+
var typing: Array = GdhDeclared.object_typing(str(kind[0]))
|
|
583
|
+
var key_type: int = GdhRead.as_int(GdhDeclared.elements_of(declared)[0], TYPE_NIL)
|
|
584
|
+
if typing.is_empty() or key_type == TYPE_OBJECT:
|
|
585
|
+
return [true, entries]
|
|
586
|
+
var base: StringName = typing[0]
|
|
587
|
+
var script: Script = typing[1]
|
|
588
|
+
return [true, Dictionary(entries, key_type, &"", null, TYPE_OBJECT, base, script)]
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
## [param given], an object for a typed list or map, as [ok, object or why not]: a resource's
|
|
592
|
+
## res:// path is the resource, and for nodes a path from [param target] is the node there.
|
|
593
|
+
## [param holding] says what the list or map holds, and [param place] is [where the value sits in
|
|
594
|
+
## what was given, what it is called in the property].
|
|
595
|
+
func _object_item(target: Object, given: Variant, kind: Array, holding: String, place: Array) -> Array:
|
|
596
|
+
var item: Variant = given
|
|
597
|
+
if typeof(item) == TYPE_STRING:
|
|
598
|
+
var found: Array = (
|
|
599
|
+
_node_reference(target, str(place[1]), item)
|
|
600
|
+
if kind[1]
|
|
601
|
+
else _resource_at(str(item), str(place[1]))
|
|
602
|
+
)
|
|
603
|
+
if not found[0]:
|
|
604
|
+
return found
|
|
605
|
+
item = found[1]
|
|
606
|
+
var object: Object = item if item is Object else null
|
|
607
|
+
if item == null or (object != null and GdhDeclared.is_a(object, str(kind[0]))):
|
|
608
|
+
return [true, item]
|
|
609
|
+
var named: String = object.get_class() if object != null else shown(item)
|
|
610
|
+
return [false, "%s, and %s is %s." % [holding, place[0], named]]
|
|
554
611
|
|
|
555
612
|
|
|
556
613
|
## Whether [param value] holds a number with a fractional part where an integer is wanted, in a
|
|
@@ -587,7 +644,7 @@ func _fraction_refusal(target: Object, property: String, raw: Variant) -> String
|
|
|
587
644
|
"%s.%s holds whole numbers, and %s would be cut to fit. Setting it would save a different "
|
|
588
645
|
+ "value than the one asked for."
|
|
589
646
|
)
|
|
590
|
-
% [class_called(target), property, shown(raw)]
|
|
647
|
+
% [GdhPropertyPaths.class_called(target), property, shown(raw)]
|
|
591
648
|
)
|
|
592
649
|
|
|
593
650
|
|
|
@@ -612,16 +669,15 @@ func colour_from(raw: Variant, named: String) -> Array:
|
|
|
612
669
|
]
|
|
613
670
|
|
|
614
671
|
|
|
615
|
-
##
|
|
616
|
-
##
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
return target.get_class()
|
|
672
|
+
## The refusal for [param property], which [param target] does not declare, naming the declared
|
|
673
|
+
## ones nearest it: "StyleBoxFlat has no property shadow_sizee" left the caller nothing to send.
|
|
674
|
+
static func no_property(target: Object, property: String) -> String:
|
|
675
|
+
return (
|
|
676
|
+
GdhRead.near_property(
|
|
677
|
+
"%s has no property %s" % [GdhPropertyPaths.class_called(target), property], target, property
|
|
678
|
+
)
|
|
679
|
+
+ "."
|
|
680
|
+
)
|
|
625
681
|
|
|
626
682
|
|
|
627
683
|
## The resource [param path] names, as [found, resource or why not].
|
|
@@ -669,15 +725,10 @@ func _resource_at(path: String, property: String) -> Array:
|
|
|
669
725
|
## its name, so without this a typo or a heading saved a file that had not changed and reported it
|
|
670
726
|
## as one that had.
|
|
671
727
|
func declaration(target: Object, property: String) -> Dictionary:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
continue
|
|
675
|
-
if GdhRead.as_int(declared.get("usage", 0)) & HEADINGS:
|
|
676
|
-
continue
|
|
677
|
-
return declared
|
|
678
|
-
if target is Theme:
|
|
728
|
+
var declared: Dictionary = GdhPropertyPaths.declared_on(target, property)
|
|
729
|
+
if declared.is_empty() and target is Theme:
|
|
679
730
|
return _theme_item(property)
|
|
680
|
-
return
|
|
731
|
+
return declared
|
|
681
732
|
|
|
682
733
|
|
|
683
734
|
## The entry a Theme would declare for [param property] once it held that item, or {} when the name
|
|
@@ -696,107 +747,6 @@ static func _theme_item(property: String) -> Dictionary:
|
|
|
696
747
|
return made
|
|
697
748
|
|
|
698
749
|
|
|
699
|
-
## Whether [param held] is [param given], as a property read back after a set answers it.
|
|
700
|
-
##
|
|
701
|
-
## Close enough rather than equal for anything made of floats: a Vector2 property stores 32-bit
|
|
702
|
-
## components, so 0.1 comes back as 0.100000001. A packed array read back against the list it was
|
|
703
|
-
## given compares element by element, since `==` between the two is an error rather than false.
|
|
704
|
-
static func same(held: Variant, given: Variant) -> bool:
|
|
705
|
-
var held_type: int = typeof(held)
|
|
706
|
-
var given_type: int = typeof(given)
|
|
707
|
-
# An empty object property reads back as an Object that is null, not as nil, and the two are
|
|
708
|
-
# the same nothing.
|
|
709
|
-
var held_none: bool = held_type == TYPE_NIL or (held_type == TYPE_OBJECT and held == null)
|
|
710
|
-
var given_none: bool = given_type == TYPE_NIL or (given_type == TYPE_OBJECT and given == null)
|
|
711
|
-
if held_none or given_none:
|
|
712
|
-
return held_none == given_none
|
|
713
|
-
if held_type == TYPE_OBJECT or given_type == TYPE_OBJECT:
|
|
714
|
-
return _same_object(held, given)
|
|
715
|
-
if GdhSerialisation.NUMBERS.has(held_type) and GdhSerialisation.NUMBERS.has(given_type):
|
|
716
|
-
if held_type == TYPE_BOOL or given_type == TYPE_BOOL:
|
|
717
|
-
return held_type == given_type and held == given
|
|
718
|
-
var held_number: float = GdhRead.as_float(held)
|
|
719
|
-
var given_number: float = GdhRead.as_float(given)
|
|
720
|
-
return (is_nan(held_number) and is_nan(given_number)) or is_equal_approx(held_number, given_number)
|
|
721
|
-
if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
|
|
722
|
-
return str(held) == str(given)
|
|
723
|
-
if _listed(held_type) and _listed(given_type):
|
|
724
|
-
var held_items: Array = type_convert(held, TYPE_ARRAY)
|
|
725
|
-
var given_items: Array = type_convert(given, TYPE_ARRAY)
|
|
726
|
-
if held_items.size() != given_items.size():
|
|
727
|
-
return false
|
|
728
|
-
for index: int in held_items.size():
|
|
729
|
-
if not same(held_items[index], given_items[index]):
|
|
730
|
-
return false
|
|
731
|
-
return true
|
|
732
|
-
if held_type == TYPE_DICTIONARY and given_type == TYPE_DICTIONARY:
|
|
733
|
-
var held_entries: Dictionary = held
|
|
734
|
-
var given_entries: Dictionary = given
|
|
735
|
-
return _same_entries(held_entries, given_entries)
|
|
736
|
-
if held_type != given_type:
|
|
737
|
-
return false
|
|
738
|
-
return _same_shape(held, given)
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
static func _same_object(held: Variant, given: Variant) -> bool:
|
|
742
|
-
if typeof(held) != TYPE_OBJECT or typeof(given) != TYPE_OBJECT:
|
|
743
|
-
return false
|
|
744
|
-
if is_same(held, given):
|
|
745
|
-
return true
|
|
746
|
-
if not held is Resource or not given is Resource:
|
|
747
|
-
return false
|
|
748
|
-
var held_resource: Resource = held
|
|
749
|
-
var given_resource: Resource = given
|
|
750
|
-
if not held_resource.resource_path.is_empty():
|
|
751
|
-
return held_resource.resource_path == given_resource.resource_path
|
|
752
|
-
# A resource local to the scene is duplicated for every instance, so the copy read back is
|
|
753
|
-
# another object of the same class.
|
|
754
|
-
return held_resource.resource_local_to_scene and held_resource.get_class() == given_resource.get_class()
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
static func _same_entries(held: Dictionary, given: Dictionary) -> bool:
|
|
758
|
-
if held.size() != given.size():
|
|
759
|
-
return false
|
|
760
|
-
for key: Variant in given:
|
|
761
|
-
var found: bool = false
|
|
762
|
-
for held_key: Variant in held:
|
|
763
|
-
if same(held_key, key):
|
|
764
|
-
found = same(held[held_key], given[key])
|
|
765
|
-
break
|
|
766
|
-
if not found:
|
|
767
|
-
return false
|
|
768
|
-
return true
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
## Two values of one type made of floats, compared component by component through the type's own
|
|
772
|
-
## text form, which lists every component: each type has its own is_equal_approx, and reaching it
|
|
773
|
-
## through a Variant is a call the typed gate refuses.
|
|
774
|
-
static func _same_shape(held: Variant, given: Variant) -> bool:
|
|
775
|
-
if typeof(held) not in FLOAT_SHAPES:
|
|
776
|
-
return held == given
|
|
777
|
-
var held_parts: Array[float] = _components(held)
|
|
778
|
-
var given_parts: Array[float] = _components(given)
|
|
779
|
-
if held_parts.size() != given_parts.size():
|
|
780
|
-
return false
|
|
781
|
-
for index: int in held_parts.size():
|
|
782
|
-
if not same(held_parts[index], given_parts[index]):
|
|
783
|
-
return false
|
|
784
|
-
return true
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
static func _components(value: Variant) -> Array[float]:
|
|
788
|
-
var parts: Array[float] = []
|
|
789
|
-
var text: String = var_to_str(value)
|
|
790
|
-
var inner: String = text.substr(text.find("(") + 1).trim_suffix(")")
|
|
791
|
-
for part: String in inner.split(",", false):
|
|
792
|
-
parts.append(part.strip_edges().to_float())
|
|
793
|
-
return parts
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
static func _listed(type: int) -> bool:
|
|
797
|
-
return type == TYPE_ARRAY or PACKED_ELEMENTS.has(type)
|
|
798
|
-
|
|
799
|
-
|
|
800
750
|
## [param value] as a caller would write it, for a refusal to quote.
|
|
801
751
|
func shown(value: Variant) -> String:
|
|
802
752
|
return JSON.stringify(_values.serialize_value(value), "", false)
|
|
@@ -833,18 +783,22 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
833
783
|
_refused = str(found[1])
|
|
834
784
|
return [true, null]
|
|
835
785
|
return [true, found[1]]
|
|
836
|
-
# What a read answers
|
|
786
|
+
# What a read answers where a resource built into a scene recurs inside itself: a class and no
|
|
837
787
|
# path. Taken as nothing, it cleared the property it was written back to.
|
|
838
788
|
if value.has("class"):
|
|
839
789
|
_refused = (
|
|
840
790
|
(
|
|
841
|
-
"The %s given has no path:
|
|
842
|
-
+
|
|
843
|
-
+ "properties beside it.
|
|
791
|
+
"The %s given has no path: a read names a resource built into a scene by its class "
|
|
792
|
+
+ "alone where it recurs inside itself, and nothing elsewhere can refer to it. Build "
|
|
793
|
+
+ 'a new one with "_type" set to its class and its properties beside it.'
|
|
844
794
|
)
|
|
845
795
|
% str(value["class"])
|
|
846
796
|
)
|
|
847
797
|
return [true, null]
|
|
798
|
+
# A plain Resource with a script or values of its own, as a read answers one built into a scene,
|
|
799
|
+
# is one to build; taken as nothing, writing such a read back cleared the property.
|
|
800
|
+
if value.size() > 1:
|
|
801
|
+
return _parse_new_resource("Resource", value)
|
|
848
802
|
return [true, null]
|
|
849
803
|
|
|
850
804
|
|
|
@@ -852,15 +806,21 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
852
806
|
## NavigationRegion2D can arrive with its NavigationPolygon and an AnimationTree with its root
|
|
853
807
|
## state machine in the same add as any other property.
|
|
854
808
|
func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
809
|
+
var built: Resource = null
|
|
855
810
|
if (
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
or not ClassDB.can_instantiate(type_tag)
|
|
811
|
+
ClassDB.class_exists(type_tag)
|
|
812
|
+
and ClassDB.is_parent_class(type_tag, "Resource")
|
|
813
|
+
and ClassDB.can_instantiate(type_tag)
|
|
860
814
|
):
|
|
815
|
+
built = ClassDB.instantiate(type_tag)
|
|
816
|
+
elif not type_tag.is_empty() and not ClassDB.class_exists(type_tag):
|
|
817
|
+
# A script's class_name, as a read answers it under scriptClass.
|
|
818
|
+
var script: Script = GdhDeclared.script_class(type_tag)
|
|
819
|
+
if script != null and ClassDB.is_parent_class(script.get_instance_base_type(), "Resource"):
|
|
820
|
+
built = ClassDB.instantiate(script.get_instance_base_type())
|
|
821
|
+
built.set_script(script)
|
|
822
|
+
if built == null:
|
|
861
823
|
return [false, null]
|
|
862
|
-
|
|
863
|
-
var built: Resource = ClassDB.instantiate(type_tag)
|
|
864
824
|
var wanted: Dictionary = {}
|
|
865
825
|
for key: Variant in value:
|
|
866
826
|
var property: String = str(key)
|
|
@@ -870,6 +830,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
870
830
|
if not refused.is_empty():
|
|
871
831
|
_refused = refused
|
|
872
832
|
return [true, null]
|
|
833
|
+
GdhDeclared.unmade(built, wanted)
|
|
873
834
|
return [true, built]
|
|
874
835
|
|
|
875
836
|
|
|
@@ -189,6 +189,21 @@ static func offered(said: String, word: String, names: Array) -> String:
|
|
|
189
189
|
return "%s; the nearest it has %s %s" % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
## [param said], the refusal for [param property], which [param target] does not declare, with the
|
|
193
|
+
## names it declares nearest it when any are, as a misspelt one is usually one of them. Unlike
|
|
194
|
+
## [method offered], nothing is listed when none is near, since an object declares a hundred.
|
|
195
|
+
static func near_property(said: String, target: Object, property: String) -> String:
|
|
196
|
+
var headings: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP
|
|
197
|
+
var names: Array[String] = []
|
|
198
|
+
for declared: Dictionary in target.get_property_list():
|
|
199
|
+
if not as_int(declared.get("usage", 0)) & headings:
|
|
200
|
+
names.append(str(declared.get("name", "")))
|
|
201
|
+
var close: PackedStringArray = nearest(property, PackedStringArray(names))
|
|
202
|
+
if close.is_empty():
|
|
203
|
+
return said
|
|
204
|
+
return "%s; the nearest it has %s %s" % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
|
|
205
|
+
|
|
206
|
+
|
|
192
207
|
## The signals [param object] has that a caller can connect, its own class's first: the engine
|
|
193
208
|
## lists them from Object down, which puts a Button's pressed after thirty a Control has, and names
|
|
194
209
|
## starting with an underscore are the engine's own.
|