From df79065b0452608329d3e64cfc37b5ecf3f09983 Mon Sep 17 00:00:00 2001 From: Daylily-Zeleen Date: Tue, 13 Jun 2023 19:20:25 +0800 Subject: [PATCH] Implement ClassDB singleton --- binding_generator.py | 172 ++++++++++++++++++++++------ include/godot_cpp/core/class_db.hpp | 7 +- src/core/class_db.cpp | 10 ++ 3 files changed, 154 insertions(+), 35 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index 3a00049b4..78684c830 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -97,9 +97,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: @@ -1036,9 +1033,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 @@ -1048,9 +1042,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() @@ -1219,6 +1210,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us class_name = class_api["name"] snake_class_name = camel_to_snake(class_name).upper() is_singleton = class_name in singletons + is_class_db = class_name == "ClassDB" add_header(f"{snake_class_name.lower()}.hpp", result) @@ -1239,14 +1231,18 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") if class_name != "Object": - result.append("#include ") - result.append("") - result.append("#include ") - result.append("") + if not is_class_db: + result.append("#include ") + result.append("") + result.append("#include ") + result.append("") result.append("namespace godot {") result.append("") + if is_class_db: + result.append("#define CLASSDB_HEADER_DECLARE()") + for type_name in used_classes: if is_struct_type(type_name): result.append(f"struct {type_name};") @@ -1257,7 +1253,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped" - result.append(f"class {class_name} : public {inherits} {{") + if not is_class_db: + result.append(f"class {class_name} : public {inherits} {{") result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})") result.append("") @@ -1338,7 +1335,12 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("\t}") result.append("") - result.append("public:") + + if is_class_db: + result.append("private:") + result.append(get_class_db_macro_end_mark_line("CLASSDB_HEADER_DECLARE")) + else: + result.append("public:") # Special cases. if class_name == "XMLParser": @@ -1378,8 +1380,9 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us "\tT *get_node(const NodePath &p_path) const { return Object::cast_to(get_node_internal(p_path)); }" ) - result.append("") - result.append("};") + if not is_class_db: + result.append("") + result.append("};") result.append("") if class_name == "EditorPlugin": @@ -1403,6 +1406,9 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("} // namespace godot") result.append("") + if is_class_db: + result.append("#define CLASSDB_HEADER_ENUM()") + if "enums" in class_api and class_name != "Object": for enum_api in class_api["enums"]: if enum_api["is_bitfield"]: @@ -1411,8 +1417,17 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(f'VARIANT_ENUM_CAST({class_name}::{enum_api["name"]});') result.append("") + if is_class_db: + result.append(get_class_db_macro_end_mark_line("CLASSDB_HEADER_ENUM")) + result.append("") + result.append(f"#include ") + result.append("") + result.append(f"#endif // ! {header_guard}") + if is_class_db: + append_backslash_to_class_db_macro(result) + return "\n".join(result) @@ -1423,13 +1438,16 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us class_name = class_api["name"] snake_class_name = camel_to_snake(class_name) is_singleton = class_name in singletons + is_class_db = class_name == "ClassDB" add_header(f"{snake_class_name}.cpp", result) - result.append(f"#include ") - result.append("") - result.append("#include ") - result.append("#include ") + if not is_class_db: + result.append(f"#include ") + result.append("") + result.append("#include ") + result.append("#include ") + result.append("") for included in used_classes: @@ -1442,21 +1460,43 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("") if is_singleton: - result.append(f"{class_name} *{class_name}::get_singleton() {{") - result.append(f"\tconst StringName __class_name = {class_name}::get_class_static();") - result.append( - "\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(__class_name._native_ptr());" - ) - result.append("#ifdef DEBUG_ENABLED") - result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") - result.append("#endif // DEBUG_ENABLED") - result.append( - f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));" - ) - result.append("\treturn singleton;") - result.append("}") + for i in range(2 if is_class_db else 1): + if is_class_db: + if i == 0: + result.append("#define CLASSDB_GET_SINGLETON_DEBUG()") + else: + result.append("#define CLASSDB_GET_SINGLETON()") + + result.append(f"{class_name} *{class_name}::get_singleton() {{") + result.append(f"\tconst StringName __class_name = {class_name}::get_class_static();") + result.append( + "\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(__class_name._native_ptr());", + ) + if not is_class_db: + result.append("#ifdef DEBUG_ENABLED") + if i == 0: + result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") + if not is_class_db: + result.append("#endif // DEBUG_ENABLED") + + result.append( + f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));", + ) + result.append("\treturn singleton;") + result.append("}") + + if is_class_db: + result.append( + get_class_db_macro_end_mark_line( + "CLASSDB_GET_SINGLETON_DEBUG" if i == 0 else "CLASSDB_GET_SINGLETON" + ) + ) + result.append("") + result.append("") + if is_class_db: + result.append("#define CLASSDB_SOURCE_IMPLEMENT()") if "methods" in class_api: for method in class_api["methods"]: if method["is_virtual"]: @@ -1569,9 +1609,15 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append(method_signature) result.append("") + if is_class_db: + result.append(get_class_db_macro_end_mark_line("CLASSDB_SOURCE_IMPLEMENT")) + result.append("") result.append("} // namespace godot ") + if is_class_db: + append_backslash_to_class_db_macro(result) + return "\n".join(result) @@ -2275,6 +2321,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] @@ -2376,3 +2423,60 @@ def add_header(filename, lines): lines.append("// THIS FILE IS GENERATED. EDITS WILL BE LOST.") lines.append("") + + +def calculate_line_length_with_table(line, tab_len): + tabs_len = 0 + for c in line: + if c == "\t": + tabs_len += tab_len - 1 + return len(line) + tabs_len + + +def add_backslash(line, backslash_pos, tab_len): + for i in range(calculate_line_length_with_table(line, tab_len), backslash_pos): + line += " " + return line + "\\" + + +def get_class_db_macro_end_mark(): + return "// Macro end mark: " + + +def get_class_db_macro_end_mark_line(macros_name): + return get_class_db_macro_end_mark() + macros_name + + +def append_backslash_to_class_db_macro(lines, tab_len=4): + for i in range(len(lines)): + if lines[i].startswith("#define CLASSDB_"): + # Find finish mark. + finish_mark_line = -1 + for j in range(i, len(lines)): + if lines[j].startswith(get_class_db_macro_end_mark()): + finish_mark_line = j + break + if finish_mark_line - i < 2: + continue + + # Find backslash slash end line. + backslash_end_line = finish_mark_line - 1 + for j in range(backslash_end_line, i, -1): + if lines[j] != "": + backslash_end_line = j + break + + # Find the max length of code block. + backslash_pos = 0 + for j in range(i, backslash_end_line): + length = calculate_line_length_with_table(lines[j], tab_len) + if length > backslash_pos: + backslash_pos = length + backslash_pos += tab_len - (backslash_pos % tab_len) + + # Add backslash. + for j in range(i, backslash_end_line): + lines[j] = add_backslash(lines[j], backslash_pos, tab_len) + + # Skip to finish mark line. + i = finish_mark_line diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp index 46fbd42db..d97f19971 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 { @@ -73,7 +75,9 @@ MethodDefinition D_METHOD(StringName p_name, StringName p_arg1, Args... args) { return md; } -class ClassDB { +class ClassDB : public Object { + CLASSDB_HEADER_DECLARE() +private: static GDExtensionInitializationLevel current_level; friend class godot::GDExtensionBinding; @@ -145,6 +149,7 @@ class ClassDB { static void initialize(GDExtensionInitializationLevel p_level); static void deinitialize(GDExtensionInitializationLevel p_level); }; +CLASSDB_HEADER_ENUM() #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 551cb8906..3353664dd 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() +#else +CLASSDB_GET_SINGLETON() +#endif + +CLASSDB_SOURCE_IMPLEMENT() + std::unordered_map ClassDB::classes; std::unordered_map ClassDB::instance_binding_callbacks; GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;