gdharness 1.3.3 → 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 +105 -74
- package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
- 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/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 +195 -221
- 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/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/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 +3 -2
- package/build/godot/operations/declared_values.gd +157 -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 +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 +195 -221
- 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
|
|
@@ -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
|
|
@@ -418,10 +428,17 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
418
428
|
if chosen == null:
|
|
419
429
|
var hinted: Dictionary = GdhDeclared.enum_values(str(declared.get("hint_string", "")))
|
|
420
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))
|
|
421
435
|
if chosen != null:
|
|
422
436
|
return [true, chosen]
|
|
423
437
|
if not str(raw).strip_edges().is_valid_int():
|
|
424
|
-
|
|
438
|
+
var why: String = GdhDeclared.enum_refusal(
|
|
439
|
+
GdhPropertyPaths.class_called(target), property, str(raw), choices, constants
|
|
440
|
+
)
|
|
441
|
+
return [false, why]
|
|
425
442
|
|
|
426
443
|
# A resource-valued property takes the path of one, which is how a caller names a TileSet,
|
|
427
444
|
# a material or a theme: there is no other way to hand a tool a Resource.
|
|
@@ -432,17 +449,28 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
432
449
|
return found
|
|
433
450
|
value = found[1]
|
|
434
451
|
else:
|
|
435
|
-
var elements: Array = elements_of(declared)
|
|
452
|
+
var elements: Array = GdhDeclared.elements_of(declared)
|
|
436
453
|
if _fraction(raw, expected_type, elements):
|
|
437
454
|
return [false, _fraction_refusal(target, property, raw)]
|
|
438
455
|
var rounded: String = GdhSerialisation.inexact(raw, expected_type)
|
|
439
456
|
if not rounded.is_empty():
|
|
440
|
-
return [
|
|
457
|
+
return [
|
|
458
|
+
false, "%s.%s holds int: %s." % [GdhPropertyPaths.class_called(target), property, rounded]
|
|
459
|
+
]
|
|
441
460
|
_refused = ""
|
|
442
461
|
value = parse(raw, expected_type, elements)
|
|
443
462
|
if not _refused.is_empty():
|
|
444
463
|
return [false, _refused]
|
|
445
|
-
|
|
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
|
+
)
|
|
446
474
|
if not refusal.is_empty():
|
|
447
475
|
return [false, refusal]
|
|
448
476
|
if value is Object:
|
|
@@ -497,60 +525,102 @@ func _wrong_class(value: Object, declared: Dictionary) -> String:
|
|
|
497
525
|
return ""
|
|
498
526
|
var names: PackedStringArray = wanted.split(",", false)
|
|
499
527
|
for name: String in names:
|
|
500
|
-
if
|
|
528
|
+
if GdhDeclared.is_a(value, name.strip_edges()):
|
|
501
529
|
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
530
|
return (
|
|
508
531
|
"%s holds %s, and the value given is a %s."
|
|
509
532
|
% [str(declared.get("name", "")), " or ".join(names), value.get_class()]
|
|
510
533
|
)
|
|
511
534
|
|
|
512
535
|
|
|
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
|
-
|
|
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]]
|
|
554
624
|
|
|
555
625
|
|
|
556
626
|
## Whether [param value] holds a number with a fractional part where an integer is wanted, in a
|
|
@@ -587,7 +657,7 @@ func _fraction_refusal(target: Object, property: String, raw: Variant) -> String
|
|
|
587
657
|
"%s.%s holds whole numbers, and %s would be cut to fit. Setting it would save a different "
|
|
588
658
|
+ "value than the one asked for."
|
|
589
659
|
)
|
|
590
|
-
% [class_called(target), property, shown(raw)]
|
|
660
|
+
% [GdhPropertyPaths.class_called(target), property, shown(raw)]
|
|
591
661
|
)
|
|
592
662
|
|
|
593
663
|
|
|
@@ -612,16 +682,15 @@ func colour_from(raw: Variant, named: String) -> Array:
|
|
|
612
682
|
]
|
|
613
683
|
|
|
614
684
|
|
|
615
|
-
##
|
|
616
|
-
##
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
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
|
+
)
|
|
625
694
|
|
|
626
695
|
|
|
627
696
|
## The resource [param path] names, as [found, resource or why not].
|
|
@@ -669,15 +738,10 @@ func _resource_at(path: String, property: String) -> Array:
|
|
|
669
738
|
## its name, so without this a typo or a heading saved a file that had not changed and reported it
|
|
670
739
|
## as one that had.
|
|
671
740
|
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:
|
|
741
|
+
var declared: Dictionary = GdhPropertyPaths.declared_on(target, property)
|
|
742
|
+
if declared.is_empty() and target is Theme:
|
|
679
743
|
return _theme_item(property)
|
|
680
|
-
return
|
|
744
|
+
return declared
|
|
681
745
|
|
|
682
746
|
|
|
683
747
|
## The entry a Theme would declare for [param property] once it held that item, or {} when the name
|
|
@@ -696,107 +760,6 @@ static func _theme_item(property: String) -> Dictionary:
|
|
|
696
760
|
return made
|
|
697
761
|
|
|
698
762
|
|
|
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
763
|
## [param value] as a caller would write it, for a refusal to quote.
|
|
801
764
|
func shown(value: Variant) -> String:
|
|
802
765
|
return JSON.stringify(_values.serialize_value(value), "", false)
|
|
@@ -833,18 +796,22 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
833
796
|
_refused = str(found[1])
|
|
834
797
|
return [true, null]
|
|
835
798
|
return [true, found[1]]
|
|
836
|
-
# What a read answers
|
|
799
|
+
# What a read answers where a resource built into a scene recurs inside itself: a class and no
|
|
837
800
|
# path. Taken as nothing, it cleared the property it was written back to.
|
|
838
801
|
if value.has("class"):
|
|
839
802
|
_refused = (
|
|
840
803
|
(
|
|
841
|
-
"The %s given has no path:
|
|
842
|
-
+
|
|
843
|
-
+ "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.'
|
|
844
807
|
)
|
|
845
808
|
% str(value["class"])
|
|
846
809
|
)
|
|
847
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)
|
|
848
815
|
return [true, null]
|
|
849
816
|
|
|
850
817
|
|
|
@@ -852,15 +819,21 @@ func _parse_resource_tag(value: Dictionary) -> Array:
|
|
|
852
819
|
## NavigationRegion2D can arrive with its NavigationPolygon and an AnimationTree with its root
|
|
853
820
|
## state machine in the same add as any other property.
|
|
854
821
|
func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
822
|
+
var built: Resource = null
|
|
855
823
|
if (
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
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)
|
|
860
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:
|
|
861
836
|
return [false, null]
|
|
862
|
-
|
|
863
|
-
var built: Resource = ClassDB.instantiate(type_tag)
|
|
864
837
|
var wanted: Dictionary = {}
|
|
865
838
|
for key: Variant in value:
|
|
866
839
|
var property: String = str(key)
|
|
@@ -870,6 +843,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
870
843
|
if not refused.is_empty():
|
|
871
844
|
_refused = refused
|
|
872
845
|
return [true, null]
|
|
846
|
+
GdhDeclared.unmade(built, wanted)
|
|
873
847
|
return [true, built]
|
|
874
848
|
|
|
875
849
|
|
|
@@ -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.
|