240 lines
8.1 KiB
GDScript
240 lines
8.1 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
## Builds stable, JSON-safe metadata for any class registered in ClassDB.
|
|
|
|
const VariantSerializer := preload("res://addons/godot_ai/utils/variant_serializer.gd")
|
|
|
|
const DEFAULT_SECTIONS := ["properties", "methods", "signals", "enums", "constants"]
|
|
const KNOWN_SECTIONS := ["properties", "methods", "signals", "enums", "constants", "inheritors"]
|
|
const MAX_DEFAULT_ITEMS := 100
|
|
|
|
|
|
static func build(type_name: String, options: Dictionary = {}) -> Dictionary:
|
|
var sections := _sections(options.get("sections", DEFAULT_SECTIONS))
|
|
var include_inherited := bool(options.get("include_inherited", false))
|
|
var include_inheritors := bool(options.get("include_inheritors", false))
|
|
var offset := max(0, int(options.get("offset", 0)))
|
|
var limit := int(options.get("limit", MAX_DEFAULT_ITEMS))
|
|
if limit < 0:
|
|
limit = MAX_DEFAULT_ITEMS
|
|
var can_instantiate := ClassDB.can_instantiate(type_name)
|
|
|
|
var data := {
|
|
"class_name": type_name,
|
|
"engine_version": Engine.get_version_info().get("string", ""),
|
|
"parent_class": str(ClassDB.get_parent_class(type_name)),
|
|
"inheritance_chain": _inheritance_chain(type_name),
|
|
"can_instantiate": can_instantiate,
|
|
"is_singleton": Engine.has_singleton(type_name),
|
|
"include_inherited": include_inherited,
|
|
"offset": offset,
|
|
"limit": limit,
|
|
}
|
|
if include_inheritors or sections.has("inheritors"):
|
|
_add_paged(data, "inheritor", "inheritors", _inheritors(type_name, false), offset, limit)
|
|
_add_paged(
|
|
data,
|
|
"concrete_inheritor",
|
|
"concrete_inheritors",
|
|
_inheritors(type_name, true),
|
|
offset,
|
|
limit
|
|
)
|
|
if sections.has("properties"):
|
|
_add_paged(data, "property", "properties", _properties(type_name, include_inherited), offset, limit)
|
|
if sections.has("methods"):
|
|
_add_paged(data, "method", "methods", _methods(type_name, include_inherited), offset, limit)
|
|
if sections.has("signals"):
|
|
_add_paged(data, "signal", "signals", _signals(type_name, include_inherited), offset, limit)
|
|
if sections.has("enums"):
|
|
_add_paged(data, "enum", "enums", _enums(type_name, include_inherited), offset, limit)
|
|
if sections.has("constants"):
|
|
_add_paged(
|
|
data,
|
|
"constant",
|
|
"constants",
|
|
_unscoped_constants(type_name, include_inherited),
|
|
offset,
|
|
limit
|
|
)
|
|
return data
|
|
|
|
|
|
static func validate_sections(raw_sections: Variant) -> Dictionary:
|
|
var sections := _sections(raw_sections)
|
|
var invalid: Array[String] = []
|
|
for section in sections:
|
|
if not KNOWN_SECTIONS.has(section):
|
|
invalid.append(section)
|
|
return {"sections": sections, "invalid": invalid}
|
|
|
|
|
|
static func _inheritance_chain(type_name: String) -> Array[String]:
|
|
var chain: Array[String] = []
|
|
var current := type_name
|
|
while not current.is_empty():
|
|
chain.append(current)
|
|
current = str(ClassDB.get_parent_class(current))
|
|
return chain
|
|
|
|
|
|
static func _sections(raw_sections: Variant) -> Array[String]:
|
|
var result: Array[String] = []
|
|
var values: Array = []
|
|
if raw_sections is String:
|
|
values = raw_sections.split(",", false)
|
|
elif raw_sections is Array:
|
|
values = raw_sections
|
|
else:
|
|
values = DEFAULT_SECTIONS
|
|
for raw_section in values:
|
|
var section := str(raw_section).strip_edges().to_lower()
|
|
if not section.is_empty() and not result.has(section):
|
|
result.append(section)
|
|
if result.is_empty():
|
|
result.assign(DEFAULT_SECTIONS)
|
|
return result
|
|
|
|
|
|
static func _add_paged(
|
|
data: Dictionary,
|
|
singular: String,
|
|
key: String,
|
|
items: Array,
|
|
offset: int,
|
|
limit: int
|
|
) -> void:
|
|
var end := items.size() if limit == 0 else min(items.size(), offset + limit)
|
|
var page: Array = []
|
|
if offset < items.size():
|
|
page = items.slice(offset, end)
|
|
data[key] = page
|
|
data["%s_count" % singular] = items.size()
|
|
data["%s_returned_count" % singular] = page.size()
|
|
|
|
|
|
static func _inheritors(type_name: String, concrete_only: bool) -> Array[String]:
|
|
var result: Array[String] = []
|
|
for inheritor in ClassDB.get_inheriters_from_class(type_name):
|
|
var inheritor_name := str(inheritor)
|
|
if concrete_only and not ClassDB.can_instantiate(inheritor_name):
|
|
continue
|
|
result.append(inheritor_name)
|
|
result.sort()
|
|
return result
|
|
|
|
|
|
static func _properties(type_name: String, include_inherited: bool) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for raw_prop in ClassDB.class_get_property_list(type_name, not include_inherited):
|
|
var prop: Dictionary = raw_prop
|
|
var usage := int(prop.get("usage", 0))
|
|
if not (usage & PROPERTY_USAGE_EDITOR):
|
|
continue
|
|
var prop_name := str(prop.get("name", ""))
|
|
result.append({
|
|
"name": prop_name,
|
|
"type": type_string(int(prop.get("type", TYPE_NIL))),
|
|
"class_name": str(prop.get("class_name", "")),
|
|
"hint": int(prop.get("hint", PROPERTY_HINT_NONE)),
|
|
"hint_string": str(prop.get("hint_string", "")),
|
|
"usage": usage,
|
|
"default": VariantSerializer.serialize(
|
|
ClassDB.class_get_property_default_value(type_name, prop_name)
|
|
),
|
|
})
|
|
result.sort_custom(func(a, b): return a.name < b.name)
|
|
return result
|
|
|
|
|
|
static func _methods(type_name: String, include_inherited: bool) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for raw_method in ClassDB.class_get_method_list(type_name, not include_inherited):
|
|
var method: Dictionary = raw_method
|
|
var args: Array[Dictionary] = []
|
|
for raw_arg in method.get("args", []):
|
|
args.append(_argument_info(raw_arg))
|
|
var defaults: Array = []
|
|
for value in method.get("default_args", []):
|
|
defaults.append(VariantSerializer.serialize(value))
|
|
result.append({
|
|
"name": str(method.get("name", "")),
|
|
"arguments": args,
|
|
"default_arguments": defaults,
|
|
"return": _argument_info(method.get("return", {})),
|
|
"flags": int(method.get("flags", 0)),
|
|
})
|
|
result.sort_custom(func(a, b): return a.name < b.name)
|
|
return result
|
|
|
|
|
|
static func _signals(type_name: String, include_inherited: bool) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for raw_signal in ClassDB.class_get_signal_list(type_name, not include_inherited):
|
|
var signal_info: Dictionary = raw_signal
|
|
var args: Array[Dictionary] = []
|
|
for raw_arg in signal_info.get("args", []):
|
|
args.append(_argument_info(raw_arg))
|
|
var defaults: Array = []
|
|
for value in signal_info.get("default_args", []):
|
|
defaults.append(VariantSerializer.serialize(value))
|
|
result.append({
|
|
"name": str(signal_info.get("name", "")),
|
|
"arguments": args,
|
|
"default_arguments": defaults,
|
|
"flags": int(signal_info.get("flags", 0)),
|
|
})
|
|
result.sort_custom(func(a, b): return a.name < b.name)
|
|
return result
|
|
|
|
|
|
static func _argument_info(raw_info: Variant) -> Dictionary:
|
|
var info: Dictionary = raw_info if raw_info is Dictionary else {}
|
|
return {
|
|
"name": str(info.get("name", "")),
|
|
"type": type_string(int(info.get("type", TYPE_NIL))),
|
|
"class_name": str(info.get("class_name", "")),
|
|
"hint": int(info.get("hint", PROPERTY_HINT_NONE)),
|
|
"hint_string": str(info.get("hint_string", "")),
|
|
"usage": int(info.get("usage", 0)),
|
|
}
|
|
|
|
|
|
static func _enums(type_name: String, include_inherited: bool) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
var enum_names: Array[String] = []
|
|
for enum_name in ClassDB.class_get_enum_list(type_name, not include_inherited):
|
|
enum_names.append(str(enum_name))
|
|
enum_names.sort()
|
|
for enum_name in enum_names:
|
|
var values: Array[Dictionary] = []
|
|
for constant_name in ClassDB.class_get_enum_constants(type_name, enum_name, not include_inherited):
|
|
values.append({
|
|
"name": str(constant_name),
|
|
"value": ClassDB.class_get_integer_constant(type_name, constant_name),
|
|
})
|
|
values.sort_custom(func(a, b): return a.name < b.name)
|
|
result.append({
|
|
"name": enum_name,
|
|
"is_bitfield": ClassDB.is_class_enum_bitfield(type_name, enum_name, not include_inherited),
|
|
"values": values,
|
|
})
|
|
return result
|
|
|
|
|
|
static func _unscoped_constants(type_name: String, include_inherited: bool) -> Array[Dictionary]:
|
|
var result: Array[Dictionary] = []
|
|
for constant_name in ClassDB.class_get_integer_constant_list(type_name, not include_inherited):
|
|
var enum_name := str(
|
|
ClassDB.class_get_integer_constant_enum(type_name, constant_name, not include_inherited)
|
|
)
|
|
if not enum_name.is_empty():
|
|
continue
|
|
result.append({
|
|
"name": str(constant_name),
|
|
"value": ClassDB.class_get_integer_constant(type_name, constant_name),
|
|
})
|
|
result.sort_custom(func(a, b): return a.name < b.name)
|
|
return result
|