gdharness 0.4.1
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/LICENSE +22 -0
- package/README.md +125 -0
- package/build/cli.js +30353 -0
- package/build/godot/addons/auto_reload/auto_reload.gd +89 -0
- package/build/godot/addons/auto_reload/plugin.cfg +6 -0
- package/build/godot/addons/gdharness_editor/bridge_client.gd +197 -0
- package/build/godot/addons/gdharness_editor/plugin.cfg +6 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +88 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +104 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +397 -0
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +85 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +427 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +885 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +292 -0
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +77 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +254 -0
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +283 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +169 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +119 -0
- package/build/godot/operations/audio_buses.gd +125 -0
- package/build/godot/operations/class_cache.gd +180 -0
- package/build/godot/operations/classdb_queries.gd +252 -0
- package/build/godot/operations/dependencies.gd +293 -0
- package/build/godot/operations/file_walk.gd +55 -0
- package/build/godot/operations/gdscript_analysis.gd +288 -0
- package/build/godot/operations/gdscript_authoring.gd +419 -0
- package/build/godot/operations/godot_operations.gd +186 -0
- package/build/godot/operations/import_pipeline.gd +390 -0
- package/build/godot/operations/input_actions.gd +237 -0
- package/build/godot/operations/logger.gd +33 -0
- package/build/godot/operations/plugins.gd +159 -0
- package/build/godot/operations/project_config.gd +153 -0
- package/build/godot/operations/project_diagnostics.gd +159 -0
- package/build/godot/operations/resource_files.gd +132 -0
- package/build/godot/operations/serialisation.gd +154 -0
- package/build/index.js +29252 -0
- package/package.json +57 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
# The editor keeps the list of global classes it knows in .godot/global_script_class_cache.cfg
|
|
4
|
+
# and refreshes it only on a filesystem scan, which it does not always do (godotengine/godot#42786).
|
|
5
|
+
# A game started from a stale editor then cannot resolve any class_name written since, and the
|
|
6
|
+
# engine reads the same file headless, so the list is rebuilt here from the scripts themselves.
|
|
7
|
+
|
|
8
|
+
const FileWalk = preload("file_walk.gd")
|
|
9
|
+
const Log = preload("logger.gd")
|
|
10
|
+
|
|
11
|
+
const CACHE_PATH: String = "res://.godot/global_script_class_cache.cfg"
|
|
12
|
+
|
|
13
|
+
var _log: Log
|
|
14
|
+
var _files: FileWalk = FileWalk.new()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
func _init(p_log: Log) -> void:
|
|
18
|
+
_log = p_log
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
func refresh_class_cache(_params: Dictionary) -> Dictionary:
|
|
22
|
+
var before: Dictionary = _entries_by_class(_read_cache())
|
|
23
|
+
|
|
24
|
+
var entries: Array = []
|
|
25
|
+
var skipped: Array[Dictionary] = []
|
|
26
|
+
for path: String in _files.find_files("res://", ".gd"):
|
|
27
|
+
var entry: Dictionary = _entry_for(path, skipped)
|
|
28
|
+
if not entry.is_empty():
|
|
29
|
+
entries.append(entry)
|
|
30
|
+
# By name as text: StringName's own order is by identity, not by spelling.
|
|
31
|
+
entries.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return str(a["class"]) < str(b["class"]))
|
|
32
|
+
|
|
33
|
+
var after: Dictionary = _entries_by_class(entries)
|
|
34
|
+
var config: ConfigFile = ConfigFile.new()
|
|
35
|
+
config.set_value("", "list", entries)
|
|
36
|
+
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(CACHE_PATH.get_base_dir()))
|
|
37
|
+
var err: Error = config.save(CACHE_PATH)
|
|
38
|
+
if err != OK:
|
|
39
|
+
return _log.failure("Failed to write " + CACHE_PATH + ": " + error_string(err))
|
|
40
|
+
|
|
41
|
+
var added: Array[String] = []
|
|
42
|
+
var removed: Array[String] = []
|
|
43
|
+
var changed: Array[String] = []
|
|
44
|
+
for name: String in after:
|
|
45
|
+
if not before.has(name):
|
|
46
|
+
added.append(name)
|
|
47
|
+
elif before[name] != after[name]:
|
|
48
|
+
changed.append(name)
|
|
49
|
+
for name: String in before:
|
|
50
|
+
if not after.has(name):
|
|
51
|
+
removed.append(name)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"path": CACHE_PATH,
|
|
55
|
+
"classes": entries.size(),
|
|
56
|
+
"added": added,
|
|
57
|
+
"removed": removed,
|
|
58
|
+
"changed": changed,
|
|
59
|
+
"skipped": skipped,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# The cache entry for one script, or empty for a script with no class_name.
|
|
64
|
+
#
|
|
65
|
+
# Read from the source rather than by loading the script, as the editor does: loading a script
|
|
66
|
+
# that extends a class_name resolves that name through the very list being rebuilt, so a stale
|
|
67
|
+
# list would fail every script written against a newer class and leave the list stale.
|
|
68
|
+
func _entry_for(path: String, skipped: Array[Dictionary]) -> Dictionary:
|
|
69
|
+
var header: Dictionary = _header_of(path)
|
|
70
|
+
var declared: String = str(header.get("class_name", ""))
|
|
71
|
+
if declared.is_empty():
|
|
72
|
+
return {}
|
|
73
|
+
|
|
74
|
+
var base: String = _base_of(path, header, skipped)
|
|
75
|
+
if base.is_empty():
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
# The field order is the editor's, so a rebuilt file reads as one the editor wrote.
|
|
79
|
+
return {
|
|
80
|
+
"base": StringName(base),
|
|
81
|
+
"class": StringName(declared),
|
|
82
|
+
"icon": str(header.get("icon", "")),
|
|
83
|
+
"is_abstract": bool(header.get("abstract", false)),
|
|
84
|
+
"is_tool": bool(header.get("tool", false)),
|
|
85
|
+
"language": StringName("GDScript"),
|
|
86
|
+
"path": path,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# What the editor records as the base: the nearest ancestor with a class_name, else the native
|
|
91
|
+
# class at the top of the chain. A script that extends by path is followed to its own header.
|
|
92
|
+
func _base_of(path: String, header: Dictionary, skipped: Array[Dictionary]) -> String:
|
|
93
|
+
var visited: Array[String] = [path]
|
|
94
|
+
var current: Dictionary = header
|
|
95
|
+
while true:
|
|
96
|
+
var extends_what: String = str(current.get("extends", ""))
|
|
97
|
+
if extends_what.is_empty():
|
|
98
|
+
return "RefCounted"
|
|
99
|
+
if not (extends_what.begins_with('"') or extends_what.begins_with("'")):
|
|
100
|
+
return extends_what
|
|
101
|
+
var parent_path: String = extends_what.substr(1, extends_what.length() - 2)
|
|
102
|
+
if not parent_path.begins_with("res://"):
|
|
103
|
+
parent_path = visited[visited.size() - 1].get_base_dir().path_join(parent_path)
|
|
104
|
+
if not FileAccess.file_exists(parent_path):
|
|
105
|
+
skipped.append({"path": path, "reason": "extends a script that does not exist: " + parent_path})
|
|
106
|
+
return ""
|
|
107
|
+
if parent_path in visited:
|
|
108
|
+
skipped.append({"path": path, "reason": "extends itself through " + parent_path})
|
|
109
|
+
return ""
|
|
110
|
+
visited.append(parent_path)
|
|
111
|
+
current = _header_of(parent_path)
|
|
112
|
+
var parent_name: String = str(current.get("class_name", ""))
|
|
113
|
+
if not parent_name.is_empty():
|
|
114
|
+
return parent_name
|
|
115
|
+
return ""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# The declarations at the top of a script: class_name, what it extends (a class name, or a
|
|
119
|
+
# quoted path), and the @tool, @abstract and @icon annotations. Reading stops at the first
|
|
120
|
+
# statement that is none of those, which is where the body begins.
|
|
121
|
+
func _header_of(path: String) -> Dictionary:
|
|
122
|
+
var header: Dictionary = {}
|
|
123
|
+
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
124
|
+
if not file:
|
|
125
|
+
return header
|
|
126
|
+
var annotation: RegEx = RegEx.new()
|
|
127
|
+
annotation.compile('^@([a-z_]+)(?:\\(\\s*(?:"([^"]*)")?[^)]*\\))?\\s*')
|
|
128
|
+
var class_line: RegEx = RegEx.new()
|
|
129
|
+
class_line.compile("^class_name\\s+([A-Za-z_][A-Za-z0-9_]*)(?:\\s+extends\\s+(\\S+))?")
|
|
130
|
+
var extends_line: RegEx = RegEx.new()
|
|
131
|
+
extends_line.compile("^extends\\s+(\\S+)")
|
|
132
|
+
while not file.eof_reached():
|
|
133
|
+
var rest: String = file.get_line().strip_edges()
|
|
134
|
+
# Annotations may share a line with what they annotate, as in `@abstract class_name X`.
|
|
135
|
+
var annotated: RegExMatch = annotation.search(rest)
|
|
136
|
+
while annotated != null:
|
|
137
|
+
var name: String = annotated.get_string(1)
|
|
138
|
+
if name == "tool":
|
|
139
|
+
header["tool"] = true
|
|
140
|
+
elif name == "abstract":
|
|
141
|
+
header["abstract"] = true
|
|
142
|
+
elif name == "icon":
|
|
143
|
+
header["icon"] = annotated.get_string(2)
|
|
144
|
+
rest = rest.substr(annotated.get_end())
|
|
145
|
+
annotated = annotation.search(rest)
|
|
146
|
+
if rest.is_empty() or rest.begins_with("#"):
|
|
147
|
+
continue
|
|
148
|
+
if rest.begins_with("class_name"):
|
|
149
|
+
var declared: RegExMatch = class_line.search(rest)
|
|
150
|
+
if declared != null:
|
|
151
|
+
header["class_name"] = declared.get_string(1)
|
|
152
|
+
if not declared.get_string(2).is_empty():
|
|
153
|
+
header["extends"] = declared.get_string(2)
|
|
154
|
+
elif rest.begins_with("extends"):
|
|
155
|
+
var parent: RegExMatch = extends_line.search(rest)
|
|
156
|
+
if parent != null:
|
|
157
|
+
header["extends"] = parent.get_string(1)
|
|
158
|
+
else:
|
|
159
|
+
break
|
|
160
|
+
file.close()
|
|
161
|
+
return header
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
func _read_cache() -> Array:
|
|
165
|
+
var config: ConfigFile = ConfigFile.new()
|
|
166
|
+
if config.load(CACHE_PATH) != OK:
|
|
167
|
+
return []
|
|
168
|
+
var list: Variant = config.get_value("", "list", [])
|
|
169
|
+
if list is Array:
|
|
170
|
+
return list
|
|
171
|
+
return []
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
func _entries_by_class(entries: Array) -> Dictionary:
|
|
175
|
+
var by_class: Dictionary = {}
|
|
176
|
+
for entry: Variant in entries:
|
|
177
|
+
if entry is Dictionary:
|
|
178
|
+
var fields: Dictionary = entry
|
|
179
|
+
by_class[str(fields.get("class", ""))] = fields
|
|
180
|
+
return by_class
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
const Log = preload("logger.gd")
|
|
4
|
+
|
|
5
|
+
# The base class each category name stands for.
|
|
6
|
+
const CATEGORY_BASES: Dictionary = {
|
|
7
|
+
"node": "Node",
|
|
8
|
+
"node2d": "Node2D",
|
|
9
|
+
"node3d": "Node3D",
|
|
10
|
+
"control": "Control",
|
|
11
|
+
"resource": "Resource",
|
|
12
|
+
"physics": "PhysicsBody3D",
|
|
13
|
+
"physics2d": "PhysicsBody2D",
|
|
14
|
+
"audio": "AudioStream",
|
|
15
|
+
"visual": "VisualInstance3D",
|
|
16
|
+
"animation": "AnimationMixer",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var _log: Log
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
func _init(p_log: Log) -> void:
|
|
23
|
+
_log = p_log
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Query available classes from ClassDB with optional filtering
|
|
27
|
+
func query_classes(params: Dictionary) -> Dictionary:
|
|
28
|
+
var filter: String = str(params.get("filter", ""))
|
|
29
|
+
var category: String = str(params.get("category", ""))
|
|
30
|
+
var instantiable_only: bool = bool(params.get("instantiable_only", false))
|
|
31
|
+
|
|
32
|
+
_log.info(
|
|
33
|
+
(
|
|
34
|
+
"Querying ClassDB classes (filter: '"
|
|
35
|
+
+ filter
|
|
36
|
+
+ "', category: '"
|
|
37
|
+
+ category
|
|
38
|
+
+ "', instantiable_only: "
|
|
39
|
+
+ str(instantiable_only)
|
|
40
|
+
+ ")"
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
var base_class: String = ""
|
|
45
|
+
if not category.is_empty():
|
|
46
|
+
base_class = CATEGORY_BASES.get(category.to_lower(), "")
|
|
47
|
+
if base_class.is_empty():
|
|
48
|
+
return _log.failure("Unknown category: " + category + ". Valid: " + str(CATEGORY_BASES.keys()))
|
|
49
|
+
|
|
50
|
+
var all_classes: PackedStringArray = ClassDB.get_class_list()
|
|
51
|
+
all_classes.sort()
|
|
52
|
+
|
|
53
|
+
var filtered_classes: Array[String] = []
|
|
54
|
+
|
|
55
|
+
for class_name_str: String in all_classes:
|
|
56
|
+
if instantiable_only and not ClassDB.can_instantiate(class_name_str):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
if not filter.is_empty() and not class_name_str.to_lower().contains(filter.to_lower()):
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
if not base_class.is_empty():
|
|
63
|
+
if not ClassDB.is_parent_class(class_name_str, base_class) and class_name_str != base_class:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
filtered_classes.append(class_name_str)
|
|
67
|
+
|
|
68
|
+
_log.info(
|
|
69
|
+
"Found " + str(filtered_classes.size()) + " classes (out of " + str(all_classes.size()) + " total)"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"total_classes": all_classes.size(),
|
|
74
|
+
"filtered_count": filtered_classes.size(),
|
|
75
|
+
"filter": filter,
|
|
76
|
+
"category": category,
|
|
77
|
+
"instantiable_only": instantiable_only,
|
|
78
|
+
"classes": filtered_classes
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Query detailed info about a specific class from ClassDB
|
|
83
|
+
func query_class_info(params: Dictionary) -> Dictionary:
|
|
84
|
+
var class_name_str: String = str(params.get("class_name", ""))
|
|
85
|
+
var include_inherited: bool = bool(params.get("include_inherited", false))
|
|
86
|
+
|
|
87
|
+
_log.info(
|
|
88
|
+
"Querying class info for: " + class_name_str + " (include_inherited: " + str(include_inherited) + ")"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if not ClassDB.class_exists(class_name_str):
|
|
92
|
+
return _log.failure("Class not found: " + class_name_str)
|
|
93
|
+
|
|
94
|
+
var methods: Array[Dictionary] = _methods_of(class_name_str, include_inherited)
|
|
95
|
+
var properties: Array[Dictionary] = _properties_of(class_name_str, include_inherited)
|
|
96
|
+
var signals: Array[Dictionary] = _signals_of(class_name_str, include_inherited)
|
|
97
|
+
|
|
98
|
+
var enums: Dictionary = {}
|
|
99
|
+
for e: String in ClassDB.class_get_enum_list(class_name_str, not include_inherited):
|
|
100
|
+
var enum_values: Dictionary = {}
|
|
101
|
+
for c: String in ClassDB.class_get_enum_constants(class_name_str, e, not include_inherited):
|
|
102
|
+
enum_values[c] = ClassDB.class_get_integer_constant(class_name_str, c)
|
|
103
|
+
enums[e] = enum_values
|
|
104
|
+
|
|
105
|
+
_log.info(
|
|
106
|
+
(
|
|
107
|
+
"Class info retrieved: "
|
|
108
|
+
+ str(methods.size())
|
|
109
|
+
+ " methods, "
|
|
110
|
+
+ str(properties.size())
|
|
111
|
+
+ " properties, "
|
|
112
|
+
+ str(signals.size())
|
|
113
|
+
+ " signals"
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"class_name": class_name_str,
|
|
119
|
+
"parent_class": ClassDB.get_parent_class(class_name_str),
|
|
120
|
+
"can_instantiate": ClassDB.can_instantiate(class_name_str),
|
|
121
|
+
"include_inherited": include_inherited,
|
|
122
|
+
"methods_count": methods.size(),
|
|
123
|
+
"methods": methods,
|
|
124
|
+
"properties_count": properties.size(),
|
|
125
|
+
"properties": properties,
|
|
126
|
+
"signals_count": signals.size(),
|
|
127
|
+
"signals": signals,
|
|
128
|
+
"enums": enums
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Inspect class inheritance hierarchy
|
|
133
|
+
func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
134
|
+
var class_name_str: String = str(params.get("class_name", ""))
|
|
135
|
+
|
|
136
|
+
_log.info("Inspecting inheritance for: " + class_name_str)
|
|
137
|
+
|
|
138
|
+
if not ClassDB.class_exists(class_name_str):
|
|
139
|
+
return _log.failure("Class not found: " + class_name_str)
|
|
140
|
+
|
|
141
|
+
var ancestors: Array[String] = []
|
|
142
|
+
var current: String = class_name_str
|
|
143
|
+
while not current.is_empty():
|
|
144
|
+
var parent: String = ClassDB.get_parent_class(current)
|
|
145
|
+
if parent.is_empty():
|
|
146
|
+
break
|
|
147
|
+
ancestors.append(parent)
|
|
148
|
+
current = parent
|
|
149
|
+
|
|
150
|
+
var all_classes: PackedStringArray = ClassDB.get_class_list()
|
|
151
|
+
|
|
152
|
+
var direct_children: Array[String] = []
|
|
153
|
+
for c: String in all_classes:
|
|
154
|
+
if ClassDB.get_parent_class(c) == class_name_str:
|
|
155
|
+
direct_children.append(c)
|
|
156
|
+
direct_children.sort()
|
|
157
|
+
|
|
158
|
+
var all_descendants: Array[String] = []
|
|
159
|
+
for c: String in all_classes:
|
|
160
|
+
if c != class_name_str and ClassDB.is_parent_class(c, class_name_str):
|
|
161
|
+
all_descendants.append(c)
|
|
162
|
+
all_descendants.sort()
|
|
163
|
+
|
|
164
|
+
_log.info(
|
|
165
|
+
(
|
|
166
|
+
"Inheritance: "
|
|
167
|
+
+ str(ancestors.size())
|
|
168
|
+
+ " ancestors, "
|
|
169
|
+
+ str(direct_children.size())
|
|
170
|
+
+ " direct children, "
|
|
171
|
+
+ str(all_descendants.size())
|
|
172
|
+
+ " total descendants"
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
"class_name": class_name_str,
|
|
178
|
+
"parent_class": ClassDB.get_parent_class(class_name_str),
|
|
179
|
+
"ancestors": ancestors,
|
|
180
|
+
"direct_children_count": direct_children.size(),
|
|
181
|
+
"direct_children": direct_children,
|
|
182
|
+
"all_descendants_count": all_descendants.size(),
|
|
183
|
+
"all_descendants": all_descendants,
|
|
184
|
+
"can_instantiate": ClassDB.can_instantiate(class_name_str)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
func _methods_of(class_name_str: String, include_inherited: bool) -> Array[Dictionary]:
|
|
189
|
+
var methods: Array[Dictionary] = []
|
|
190
|
+
|
|
191
|
+
for m: Dictionary in ClassDB.class_get_method_list(class_name_str, not include_inherited):
|
|
192
|
+
var args: Array[Dictionary] = []
|
|
193
|
+
var declared_args: Array = m.get("args", [])
|
|
194
|
+
for a: Dictionary in declared_args:
|
|
195
|
+
args.append(
|
|
196
|
+
{
|
|
197
|
+
"name": a.get("name", ""),
|
|
198
|
+
"type": a.get("type", 0),
|
|
199
|
+
"class_name": a.get("class_name", ""),
|
|
200
|
+
"hint_string": a.get("hint_string", "")
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
var return_info: Dictionary = m.get("return", {})
|
|
204
|
+
methods.append(
|
|
205
|
+
{
|
|
206
|
+
"name": m.get("name", ""),
|
|
207
|
+
"args": args,
|
|
208
|
+
"return":
|
|
209
|
+
{"type": return_info.get("type", 0), "class_name": return_info.get("class_name", "")},
|
|
210
|
+
"flags": m.get("flags", 0),
|
|
211
|
+
"default_args": m.get("default_args", [])
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return methods
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Dictionary]:
|
|
219
|
+
var properties: Array[Dictionary] = []
|
|
220
|
+
|
|
221
|
+
for p: Dictionary in ClassDB.class_get_property_list(class_name_str, not include_inherited):
|
|
222
|
+
# A category, group or subgroup is an editor heading rather than a property.
|
|
223
|
+
var usage: int = int(p.get("usage", 0))
|
|
224
|
+
if usage & PROPERTY_USAGE_CATEGORY or usage & PROPERTY_USAGE_GROUP or usage & PROPERTY_USAGE_SUBGROUP:
|
|
225
|
+
continue
|
|
226
|
+
properties.append(
|
|
227
|
+
{
|
|
228
|
+
"name": p.get("name", ""),
|
|
229
|
+
"type": p.get("type", 0),
|
|
230
|
+
"class_name": p.get("class_name", ""),
|
|
231
|
+
"hint": p.get("hint", 0),
|
|
232
|
+
"hint_string": p.get("hint_string", ""),
|
|
233
|
+
"usage": usage
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return properties
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
func _signals_of(class_name_str: String, include_inherited: bool) -> Array[Dictionary]:
|
|
241
|
+
var signals: Array[Dictionary] = []
|
|
242
|
+
|
|
243
|
+
for s: Dictionary in ClassDB.class_get_signal_list(class_name_str, not include_inherited):
|
|
244
|
+
var sig_args: Array[Dictionary] = []
|
|
245
|
+
var declared_args: Array = s.get("args", [])
|
|
246
|
+
for a: Dictionary in declared_args:
|
|
247
|
+
sig_args.append(
|
|
248
|
+
{"name": a.get("name", ""), "type": a.get("type", 0), "class_name": a.get("class_name", "")}
|
|
249
|
+
)
|
|
250
|
+
signals.append({"name": s.get("name", ""), "args": sig_args})
|
|
251
|
+
|
|
252
|
+
return signals
|