From aae9f71c7f6edb44562b4985588f6b84727a232b Mon Sep 17 00:00:00 2001 From: Daylily-Zeleen Date: Thu, 24 Nov 2022 19:55:09 +0800 Subject: [PATCH] Implement ClassDB singleton --- binding_generator.py | 514 +++++++++++++++++++++------- include/godot_cpp/core/class_db.hpp | 3 + src/core/class_db.cpp | 10 + test/src/register_types.cpp | 5 + 4 files changed, 415 insertions(+), 117 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index ad81d4d568..469b7f9e70 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -98,9 +98,6 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False): files.append(str(source_filename.as_posix())) for engine_class in api["classes"]: - # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings. - if engine_class["name"] == "ClassDB": - continue header_filename = include_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".hpp") source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp") if headers: @@ -950,9 +947,6 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): # First create map of classes and singletons. for class_api in api["classes"]: - # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings. - if class_api["name"] == "ClassDB": - continue engine_classes[class_api["name"]] = class_api["is_refcounted"] for native_struct in api["native_structures"]: engine_classes[native_struct["name"]] = False @@ -962,9 +956,6 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): singletons.append(singleton["name"]) for class_api in api["classes"]: - # TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings. - if class_api["name"] == "ClassDB": - continue # Check used classes for header include. used_classes = set() fully_used_classes = set() @@ -1138,154 +1129,429 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(f"#define {header_guard}") result.append("") + if class_name == "ClassDB": + for included in fully_used_classes: + if included == "TypedArray": + result.append("#include ") + else: + result.append(f"#include ") - for included in fully_used_classes: - if included == "TypedArray": - result.append("#include ") - else: - result.append(f"#include ") + if len(fully_used_classes) > 0: + result.append("") + + if class_name != "Object": + result.append("#include ") + result.append("") + + append_continue_line(result, "#define CLASSDB_HEADER_DEFINE(m_class, m_inherits)") + + for type_name in used_classes: + if is_struct_type(type_name): + append_continue_line(result, f"struct {type_name};") + else: + append_continue_line(result, f"class {type_name};") + + if len(used_classes) > 0: + append_continue_line(result, "") + + append_continue_line(result, f"\tGDNATIVE_CLASS(m_class, m_inherits)") + append_continue_line(result, "") + + append_continue_line(result, "public:") + append_continue_line(result, "") + + if "enums" in class_api: + for enum_api in class_api["enums"]: + append_continue_line(result, f'\tenum {enum_api["name"]} {{') + for value in enum_api["values"]: + append_continue_line(result, f'\t\t{value["name"]} = {value["value"]},') + append_continue_line(result, "\t};") + append_continue_line(result, "") + + if "constants" in class_api: + for value in class_api["constants"]: + if "type" not in value: + value["type"] = "int" + append_continue_line(result, f'\tstatic const {value["type"]} {value["name"]} = {value["value"]};') + append_continue_line(result, "") + + if is_singleton: + append_continue_line(result, f"\tstatic m_class *get_singleton();") + append_continue_line(result, "") + + if "methods" in class_api: + for method in class_api["methods"]: + if method["is_virtual"]: + # Will be done later. + continue + + vararg = "is_vararg" in method and method["is_vararg"] + + method_signature = "\t" + if vararg: + method_signature += "private: " + method_signature += make_signature( + class_name, method, for_header=True, use_template_get_node=use_template_get_node + ) + append_continue_line(result, method_signature + ";") + + if vararg: + # Add templated version. + result += make_varargs_template(method) + + # Virtuals now. + for method in class_api["methods"]: + if not method["is_virtual"]: + continue + + method_signature = "\t" + method_signature += make_signature( + class_name, method, for_header=True, use_template_get_node=use_template_get_node + ) + append_continue_line(result, method_signature + ";") + + append_continue_line(result, "protected:") + # T is the custom class we want to register (from which the call initiates, going up the inheritance chain), + # B is its base class (can be a custom class too, that's why we pass it). + append_continue_line(result, "\ttemplate ") + append_continue_line(result, "\tstatic void register_virtuals() {") + if class_name != "Object": + append_continue_line(result, f"\t\tm_inherits::register_virtuals();") + if "methods" in class_api: + for method in class_api["methods"]: + if not method["is_virtual"]: + continue + method_name = escape_identifier(method["name"]) + append_continue_line( + result, + # If the method is different from the base class, it means T overrides it, so it needs to be bound. + # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the + # condition returns false (in such cases it can't compile due to ambiguity). + f"\t\tif constexpr (!std::is_same_v) {{", + ) + append_continue_line(result, f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});") + append_continue_line(result, "\t\t}") + + append_continue_line(result, "\t}") + append_continue_line(result, "") - if len(fully_used_classes) > 0: result.append("") - if class_name != "Object": - result.append("#include ") + append_continue_line(result, "#define CLASSDB_HEADER_ENUM(m_class)") + if "enums" in class_api and class_name != "Object": + for enum_api in class_api["enums"]: + if enum_api["is_bitfield"]: + append_continue_line(result, f'VARIANT_BITFIELD_CAST(m_class, m_class::{enum_api["name"]});') + else: + append_continue_line(result, f'VARIANT_ENUM_CAST(m_class, m_class::{enum_api["name"]});') result.append("") - result.append("#include ") + + append_continue_line(result, "#define CLASSDB_GET_SINGLETON_DEBUG(m_class)") + append_continue_line(result, f"m_class *m_class::get_singleton() {{") + append_continue_line(result, f"\tconst StringName __class_name = m_class::get_class_static();") + append_continue_line( + result, + f"\tstatic GDNativeObjectPtr singleton_obj = internal::gdn_interface->global_get_singleton(__class_name._native_ptr());", + ) + append_continue_line(result, "\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") + append_continue_line( + result, + f"\tstatic m_class *singleton = reinterpret_cast(internal::gdn_interface->object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));", + ) + append_continue_line(result, "\treturn singleton;") + append_continue_line(result, "}") + append_continue_line(result, "") result.append("") - result.append("namespace godot {") - result.append("") + append_continue_line(result, "#define CLASSDB_GET_SINGLETON(m_class)") + append_continue_line(result, f"m_class *m_class::get_singleton() {{") + append_continue_line(result, f"\tconst StringName __class_name = m_class::get_class_static();") + append_continue_line( + result, + f"\tstatic GDNativeObjectPtr singleton_obj = internal::gdn_interface->global_get_singleton(__class_name._native_ptr());", + ) + append_continue_line( + result, + f"\tstatic m_class *singleton = reinterpret_cast(internal::gdn_interface->object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));", + ) + append_continue_line(result, "\treturn singleton;") + append_continue_line(result, "}") + append_continue_line(result, "") + result.append("") - for type_name in used_classes: - if is_struct_type(type_name): - result.append(f"struct {type_name};") - else: - result.append(f"class {type_name};") + append_continue_line(result, "#define CLASSDB_SOURCE_IMPLEMENT(m_class)") + append_continue_line(result, "") - if len(used_classes) > 0: - result.append("") + if len(used_classes) > 0: + append_continue_line(result, f"") - inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped" - result.append(f"class {class_name} : public {inherits} {{") + append_continue_line(result, "") - result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})") - result.append("") + if "methods" in class_api: + for method in class_api["methods"]: + if method["is_virtual"]: + # Will be done later + continue - result.append("public:") - result.append("") + vararg = "is_vararg" in method and method["is_vararg"] - if "enums" in class_api: - for enum_api in class_api["enums"]: - result.append(f'\tenum {enum_api["name"]} {{') - for value in enum_api["values"]: - result.append(f'\t\t{value["name"]} = {value["value"]},') - result.append("\t};") - result.append("") + # Method signature. + method_signature = make_signature("m_class", method, use_template_get_node=use_template_get_node) + append_continue_line(result, method_signature + " {") + + # Method body. + append_continue_line(result, f"\tconst StringName __class_name = m_class::get_class_static();") + append_continue_line(result, f'\tconst StringName __method_name = "{method["name"]}";') + append_continue_line( + result, + f'\tstatic GDNativeMethodBindPtr ___method_bind = internal::gdn_interface->classdb_get_method_bind(__class_name._native_ptr(), __method_name._native_ptr(), {method["hash"]});', + ) + method_call = "\t" + has_return = "return_value" in method and method["return_value"]["type"] != "void" + + if has_return: + append_continue_line( + result, + f'\tCHECK_METHOD_BIND_RET(___method_bind, {get_default_value_for_type(method["return_value"]["type"])});', + ) + else: + append_continue_line(result, f"\tCHECK_METHOD_BIND(___method_bind);") + + is_ref = False + if not vararg: + if has_return: + return_type = method["return_value"]["type"] + meta_type = method["return_value"]["meta"] if "meta" in method["return_value"] else None + if is_pod_type(return_type) or is_variant(return_type) or is_enum(return_type): + if method["is_static"]: + method_call += f"return internal::_call_native_mb_ret<{get_gdnative_type(correct_type(return_type, meta_type))}>(___method_bind, nullptr" + else: + method_call += f"return internal::_call_native_mb_ret<{get_gdnative_type(correct_type(return_type, meta_type))}>(___method_bind, _owner" + elif is_refcounted(return_type): + if method["is_static"]: + method_call += f"return Ref<{return_type}>::___internal_constructor(internal::_call_native_mb_ret_obj<{return_type}>(___method_bind, nullptr" + else: + method_call += f"return Ref<{return_type}>::___internal_constructor(internal::_call_native_mb_ret_obj<{return_type}>(___method_bind, _owner" + is_ref = True + else: + if method["is_static"]: + method_call += ( + f"return internal::_call_native_mb_ret_obj<{return_type}>(___method_bind, nullptr" + ) + else: + method_call += ( + f"return internal::_call_native_mb_ret_obj<{return_type}>(___method_bind, _owner" + ) + else: + if method["is_static"]: + method_call += f"internal::_call_native_mb_no_ret(___method_bind, nullptr" + else: + method_call += f"internal::_call_native_mb_no_ret(___method_bind, _owner" + + if "arguments" in method: + method_call += ", " + arguments = [] + for argument in method["arguments"]: + (encode, arg_name) = get_encoded_arg( + argument["name"], + argument["type"], + argument["meta"] if "meta" in argument else None, + ) + for line in encode: + append_continue_line(result, line) + arguments.append(arg_name) + method_call += ", ".join(arguments) + else: # vararg. + append_continue_line(result, "\tGDNativeCallError error;") + append_continue_line(result, "\tVariant ret;") + method_call += "internal::gdn_interface->object_method_bind_call(___method_bind, _owner, (const GDNativeVariantPtr *)args, arg_count, &ret, &error" + + if is_ref: + method_call += ")" # Close Ref<> constructor. + method_call += ");" + append_continue_line(result, method_call) + + if vararg and ("return_value" in method and method["return_value"]["type"] != "void"): + return_type = get_enum_fullname(method["return_value"]["type"]) + if return_type != "Variant": + append_continue_line(result, f"\treturn VariantCaster<{return_type}>::cast(ret);") + else: + append_continue_line(result, "\treturn ret;") + + append_continue_line(result, "}") + append_continue_line(result, "") + + # Virtuals now. + for method in class_api["methods"]: + if not method["is_virtual"]: + continue + + method_signature = make_signature("m_class", method, use_template_get_node=use_template_get_node) + method_signature += " {" + if "return_value" in method and correct_type(method["return_value"]["type"]) != "void": + append_continue_line(result, method_signature) + append_continue_line( + result, f'\treturn {get_default_value_for_type(method["return_value"]["type"])};' + ) + append_continue_line(result, "}") + else: + method_signature += "}" + append_continue_line(result, method_signature) + append_continue_line(result, "") + + append_continue_line(result, "") - if "constants" in class_api: - for value in class_api["constants"]: - if "type" not in value: - value["type"] = "int" - result.append(f'\tstatic const {value["type"]} {value["name"]} = {value["value"]};') result.append("") + result.append(f"#endif // ! {header_guard}") + return "\n".join(result) + else: + for included in fully_used_classes: + if included == "TypedArray": + result.append("#include ") + else: + result.append(f"#include ") - if is_singleton: - result.append(f"\tstatic {class_name} *get_singleton();") + if len(fully_used_classes) > 0: + result.append("") + + if class_name != "Object": + result.append("#include ") + result.append("") + result.append("#include ") + result.append("") + + result.append("namespace godot {") result.append("") - if "methods" in class_api: - for method in class_api["methods"]: - if method["is_virtual"]: - # Will be done later. - continue + for type_name in used_classes: + if is_struct_type(type_name): + result.append(f"struct {type_name};") + else: + result.append(f"class {type_name};") - vararg = "is_vararg" in method and method["is_vararg"] + if len(used_classes) > 0: + result.append("") - method_signature = "\t" - if vararg: - method_signature += "private: " - method_signature += make_signature( - class_name, method, for_header=True, use_template_get_node=use_template_get_node - ) - result.append(method_signature + ";") + inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped" + result.append(f"class {class_name} : public {inherits} {{") - if vararg: - # Add templated version. - result += make_varargs_template(method) + result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})") + result.append("") - # Virtuals now. - for method in class_api["methods"]: - if not method["is_virtual"]: - continue + result.append("public:") + result.append("") + + if "enums" in class_api: + for enum_api in class_api["enums"]: + result.append(f'\tenum {enum_api["name"]} {{') + for value in enum_api["values"]: + result.append(f'\t\t{value["name"]} = {value["value"]},') + result.append("\t};") + result.append("") + + if "constants" in class_api: + for value in class_api["constants"]: + if "type" not in value: + value["type"] = "int" + result.append(f'\tstatic const {value["type"]} {value["name"]} = {value["value"]};') + result.append("") + + if is_singleton: + result.append(f"\tstatic {class_name} *get_singleton();") + result.append("") - method_signature = "\t" - method_signature += make_signature( - class_name, method, for_header=True, use_template_get_node=use_template_get_node - ) - result.append(method_signature + ";") - - result.append("protected:") - # T is the custom class we want to register (from which the call initiates, going up the inheritance chain), - # B is its base class (can be a custom class too, that's why we pass it). - result.append("\ttemplate ") - result.append("\tstatic void register_virtuals() {") - if class_name != "Object": - result.append(f"\t\t{inherits}::register_virtuals();") if "methods" in class_api: for method in class_api["methods"]: - if not method["is_virtual"]: + if method["is_virtual"]: + # Will be done later. continue - method_name = escape_identifier(method["name"]) - result.append( - # If the method is different from the base class, it means T overrides it, so it needs to be bound. - # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the - # condition returns false (in such cases it can't compile due to ambiguity). - f"\t\tif constexpr (!std::is_same_v) {{" + + vararg = "is_vararg" in method and method["is_vararg"] + + method_signature = "\t" + if vararg: + method_signature += "private: " + method_signature += make_signature( + class_name, method, for_header=True, use_template_get_node=use_template_get_node ) - result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});") - result.append("\t\t}") + result.append(method_signature + ";") - result.append("\t}") - result.append("") - result.append("public:") + if vararg: + # Add templated version. + result += make_varargs_template(method) - # Special cases. - if class_name == "Object": + # Virtuals now. + for method in class_api["methods"]: + if not method["is_virtual"]: + continue + + method_signature = "\t" + method_signature += make_signature( + class_name, method, for_header=True, use_template_get_node=use_template_get_node + ) + result.append(method_signature + ";") + + result.append("protected:") + # T is the custom class we want to register (from which the call initiates, going up the inheritance chain), + # B is its base class (can be a custom class too, that's why we pass it). + result.append("\ttemplate ") + result.append("\tstatic void register_virtuals() {") + if class_name != "Object": + result.append(f"\t\t{inherits}::register_virtuals();") + if "methods" in class_api: + for method in class_api["methods"]: + if not method["is_virtual"]: + continue + method_name = escape_identifier(method["name"]) + result.append( + # If the method is different from the base class, it means T overrides it, so it needs to be bound. + # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the + # condition returns false (in such cases it can't compile due to ambiguity). + f"\t\tif constexpr (!std::is_same_v) {{" + ) + result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});") + result.append("\t\t}") + + result.append("\t}") result.append("") + result.append("public:") - result.append("\ttemplate") - result.append("\tstatic T *cast_to(Object *p_object);") + # Special cases. + if class_name == "Object": + result.append("") - result.append("\ttemplate") - result.append("\tstatic const T *cast_to(const Object *p_object);") + result.append("\ttemplate") + result.append("\tstatic T *cast_to(Object *p_object);") - result.append("\tvirtual ~Object() = default;") + result.append("\ttemplate") + result.append("\tstatic const T *cast_to(const Object *p_object);") - elif use_template_get_node and class_name == "Node": - result.append("\ttemplate") - result.append( - "\tT *get_node(const NodePath &p_path) const { return Object::cast_to(get_node_internal(p_path)); }" - ) + result.append("\tvirtual ~Object() = default;") - result.append("") - result.append("};") - result.append("") + elif use_template_get_node and class_name == "Node": + result.append("\ttemplate") + result.append( + "\tT *get_node(const NodePath &p_path) const { return Object::cast_to(get_node_internal(p_path)); }" + ) - result.append("} // namespace godot") - result.append("") + result.append("") + result.append("};") + result.append("") - if "enums" in class_api and class_name != "Object": - for enum_api in class_api["enums"]: - if enum_api["is_bitfield"]: - result.append(f'VARIANT_BITFIELD_CAST({class_name}, {class_name}::{enum_api["name"]});') - else: - result.append(f'VARIANT_ENUM_CAST({class_name}, {class_name}::{enum_api["name"]});') + result.append("} // namespace godot") result.append("") - result.append(f"#endif // ! {header_guard}") + if "enums" in class_api and class_name != "Object": + for enum_api in class_api["enums"]: + if enum_api["is_bitfield"]: + result.append(f'VARIANT_BITFIELD_CAST({class_name}, {class_name}::{enum_api["name"]});') + else: + result.append(f'VARIANT_ENUM_CAST({class_name}, {class_name}::{enum_api["name"]});') + result.append("") - return "\n".join(result) + result.append(f"#endif // ! {header_guard}") + + return "\n".join(result) def generate_engine_class_source(class_api, used_classes, fully_used_classes, use_template_get_node): @@ -1300,6 +1566,9 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us add_header(f"{snake_class_name}.cpp", result) result.append(f"#include ") + if class_name == "ClassDB": + return "\n".join(result) + result.append("") result.append(f"#include ") result.append(f"#include ") @@ -2100,6 +2369,7 @@ def escape_identifier(id): "operator": "_operator", "typeof": "type_of", "typename": "type_name", + "enum": "_enum", } if id in cpp_keywords_map: return cpp_keywords_map[id] @@ -2201,3 +2471,13 @@ def add_header(filename, lines): lines.append("// THIS FILE IS GENERATED. EDITS WILL BE LOST.") lines.append("") + + +def append_continue_line(lines, line="", slash_pos=200, tab_len=4): + tabs_len = 0 + for c in line: + if c == "\t": + tabs_len += tab_len - 1 + for i in range(len(line) + tabs_len, slash_pos): + line += " " + lines.append(line + "\\") diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 0335053cd5..d0b47bfd56 100644 --- a/include/godot_cpp/core/class_db.hpp +++ b/include/godot_cpp/core/class_db.hpp @@ -44,6 +44,8 @@ #include #include +#include + // Needed to use StringName as key in `std::unordered_map` template <> struct std::hash { @@ -141,6 +143,7 @@ class ClassDB { static void initialize(GDExtensionInitializationLevel p_level); static void deinitialize(GDExtensionInitializationLevel p_level); }; +CLASSDB_HEADER_ENUM(ClassDB) #define BIND_CONSTANT(m_constant) \ godot::ClassDB::bind_integer_constant(get_class_static(), "", #m_constant, m_constant); diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp index 54dbda7324..5fa731dfcc 100644 --- a/src/core/class_db.cpp +++ b/src/core/class_db.cpp @@ -35,10 +35,20 @@ #include +#include + #include namespace godot { +#ifdef DEBUG_ENABLED +CLASSDB_GET_SINGLETON_DEBUG(ClassDB) +#else +CLASSDB_GET_SINGLETON(ClassDB) +#endif + +CLASSDB_SOURCE_IMPLEMENT(ClassDB) + std::unordered_map ClassDB::classes; GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE; diff --git a/test/src/register_types.cpp b/test/src/register_types.cpp index e22662fc16..bd1e55f5c4 100644 --- a/test/src/register_types.cpp +++ b/test/src/register_types.cpp @@ -26,6 +26,11 @@ void initialize_example_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(true); ClassDB::register_abstract_class(); + + // ClassDB singleton test. + auto node = ClassDB::get_singleton()->instantiate("Node"); + UtilityFunctions::print("== ClassDB singleton test:", node); + Object::cast_to(node)->queue_free(); } void uninitialize_example_module(ModuleInitializationLevel p_level) {