Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ClassDB singleton #936

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(f"#include <godot_cpp/../../src/classes/{snake_class_name.lower()}.cpp>")
result.append("")

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