Skip to content

Commit

Permalink
Implement ClassDB singleton
Browse files Browse the repository at this point in the history
  • Loading branch information
Daylily-Zeleen committed Jun 13, 2023
1 parent d12cf07 commit 5940b58
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 35 deletions.
172 changes: 138 additions & 34 deletions binding_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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)

Expand All @@ -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 <godot_cpp/core/class_db.hpp>")
result.append("")
result.append("#include <type_traits>")
result.append("")
if not is_class_db:
result.append("#include <godot_cpp/core/class_db.hpp>")
result.append("")
result.append("#include <type_traits>")
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};")
Expand All @@ -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("")
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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<T>(get_node_internal(p_path)); }"
)

result.append("")
result.append("};")
if not is_class_db:
result.append("")
result.append("};")
result.append("")

if class_name == "EditorPlugin":
Expand All @@ -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"]:
Expand All @@ -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("")
result.append(f"#include <godot_cpp/../../src/classes/{snake_class_name.lower()}.cpp>")

result.append(f"#endif // ! {header_guard}")

if is_class_db:
append_backslash_to_class_db_macro(result)

return "\n".join(result)


Expand All @@ -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 <godot_cpp/classes/{snake_class_name}.hpp>")
result.append("")
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
result.append("#include <godot_cpp/core/error_macros.hpp>")
if not is_class_db:
result.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
result.append("")
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
result.append("#include <godot_cpp/core/error_macros.hpp>")

result.append("")

for included in used_classes:
Expand All @@ -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"]:
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
7 changes: 6 additions & 1 deletion include/godot_cpp/core/class_db.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
#include <unordered_map>
#include <vector>

#include <godot_cpp/classes/class_db.hpp>

// Needed to use StringName as key in `std::unordered_map`
template <>
struct std::hash<godot::StringName> {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/core/class_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@

#include <godot_cpp/core/memory.hpp>

#include <godot_cpp/core/engine_ptrcall.hpp>

#include <algorithm>

namespace godot {

#ifdef DEBUG_ENABLED
CLASSDB_GET_SINGLETON_DEBUG()
#else
CLASSDB_GET_SINGLETON()
#endif

CLASSDB_SOURCE_IMPLEMENT()

std::unordered_map<StringName, ClassDB::ClassInfo> ClassDB::classes;
std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;
Expand Down

0 comments on commit 5940b58

Please sign in to comment.