Skip to content

Commit 5940b58

Browse files
Implement ClassDB singleton
1 parent d12cf07 commit 5940b58

File tree

3 files changed

+154
-35
lines changed

3 files changed

+154
-35
lines changed

binding_generator.py

Lines changed: 138 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,6 @@ def get_file_list(api_filepath, output_dir, headers=False, sources=False):
9797
files.append(str(source_filename.as_posix()))
9898

9999
for engine_class in api["classes"]:
100-
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
101-
if engine_class["name"] == "ClassDB":
102-
continue
103100
header_filename = include_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".hpp")
104101
source_filename = source_gen_folder / "classes" / (camel_to_snake(engine_class["name"]) + ".cpp")
105102
if headers:
@@ -1036,9 +1033,6 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
10361033

10371034
# First create map of classes and singletons.
10381035
for class_api in api["classes"]:
1039-
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
1040-
if class_api["name"] == "ClassDB":
1041-
continue
10421036
engine_classes[class_api["name"]] = class_api["is_refcounted"]
10431037
for native_struct in api["native_structures"]:
10441038
engine_classes[native_struct["name"]] = False
@@ -1048,9 +1042,6 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node):
10481042
singletons.append(singleton["name"])
10491043

10501044
for class_api in api["classes"]:
1051-
# TODO: Properly setup this singleton since it conflicts with ClassDB in the bindings.
1052-
if class_api["name"] == "ClassDB":
1053-
continue
10541045
# Check used classes for header include.
10551046
used_classes = set()
10561047
fully_used_classes = set()
@@ -1219,6 +1210,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
12191210
class_name = class_api["name"]
12201211
snake_class_name = camel_to_snake(class_name).upper()
12211212
is_singleton = class_name in singletons
1213+
is_class_db = class_name == "ClassDB"
12221214

12231215
add_header(f"{snake_class_name.lower()}.hpp", result)
12241216

@@ -1239,14 +1231,18 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
12391231
result.append("")
12401232

12411233
if class_name != "Object":
1242-
result.append("#include <godot_cpp/core/class_db.hpp>")
1243-
result.append("")
1244-
result.append("#include <type_traits>")
1245-
result.append("")
1234+
if not is_class_db:
1235+
result.append("#include <godot_cpp/core/class_db.hpp>")
1236+
result.append("")
1237+
result.append("#include <type_traits>")
1238+
result.append("")
12461239

12471240
result.append("namespace godot {")
12481241
result.append("")
12491242

1243+
if is_class_db:
1244+
result.append("#define CLASSDB_HEADER_DECLARE()")
1245+
12501246
for type_name in used_classes:
12511247
if is_struct_type(type_name):
12521248
result.append(f"struct {type_name};")
@@ -1257,7 +1253,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
12571253
result.append("")
12581254

12591255
inherits = class_api["inherits"] if "inherits" in class_api else "Wrapped"
1260-
result.append(f"class {class_name} : public {inherits} {{")
1256+
if not is_class_db:
1257+
result.append(f"class {class_name} : public {inherits} {{")
12611258

12621259
result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
12631260
result.append("")
@@ -1338,7 +1335,12 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
13381335

13391336
result.append("\t}")
13401337
result.append("")
1341-
result.append("public:")
1338+
1339+
if is_class_db:
1340+
result.append("private:")
1341+
result.append(get_class_db_macro_end_mark_line("CLASSDB_HEADER_DECLARE"))
1342+
else:
1343+
result.append("public:")
13421344

13431345
# Special cases.
13441346
if class_name == "XMLParser":
@@ -1378,8 +1380,9 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
13781380
"\tT *get_node(const NodePath &p_path) const { return Object::cast_to<T>(get_node_internal(p_path)); }"
13791381
)
13801382

1381-
result.append("")
1382-
result.append("};")
1383+
if not is_class_db:
1384+
result.append("")
1385+
result.append("};")
13831386
result.append("")
13841387

13851388
if class_name == "EditorPlugin":
@@ -1403,6 +1406,9 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
14031406
result.append("} // namespace godot")
14041407
result.append("")
14051408

1409+
if is_class_db:
1410+
result.append("#define CLASSDB_HEADER_ENUM()")
1411+
14061412
if "enums" in class_api and class_name != "Object":
14071413
for enum_api in class_api["enums"]:
14081414
if enum_api["is_bitfield"]:
@@ -1411,8 +1417,17 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
14111417
result.append(f'VARIANT_ENUM_CAST({class_name}::{enum_api["name"]});')
14121418
result.append("")
14131419

1420+
if is_class_db:
1421+
result.append(get_class_db_macro_end_mark_line("CLASSDB_HEADER_ENUM"))
1422+
result.append("")
1423+
result.append("")
1424+
result.append(f"#include <godot_cpp/../../src/classes/{snake_class_name.lower()}.cpp>")
1425+
14141426
result.append(f"#endif // ! {header_guard}")
14151427

1428+
if is_class_db:
1429+
append_backslash_to_class_db_macro(result)
1430+
14161431
return "\n".join(result)
14171432

14181433

@@ -1423,13 +1438,16 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
14231438
class_name = class_api["name"]
14241439
snake_class_name = camel_to_snake(class_name)
14251440
is_singleton = class_name in singletons
1441+
is_class_db = class_name == "ClassDB"
14261442

14271443
add_header(f"{snake_class_name}.cpp", result)
14281444

1429-
result.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
1430-
result.append("")
1431-
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
1432-
result.append("#include <godot_cpp/core/error_macros.hpp>")
1445+
if not is_class_db:
1446+
result.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
1447+
result.append("")
1448+
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
1449+
result.append("#include <godot_cpp/core/error_macros.hpp>")
1450+
14331451
result.append("")
14341452

14351453
for included in used_classes:
@@ -1442,21 +1460,43 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
14421460
result.append("")
14431461

14441462
if is_singleton:
1445-
result.append(f"{class_name} *{class_name}::get_singleton() {{")
1446-
result.append(f"\tconst StringName __class_name = {class_name}::get_class_static();")
1447-
result.append(
1448-
"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(__class_name._native_ptr());"
1449-
)
1450-
result.append("#ifdef DEBUG_ENABLED")
1451-
result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);")
1452-
result.append("#endif // DEBUG_ENABLED")
1453-
result.append(
1454-
f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));"
1455-
)
1456-
result.append("\treturn singleton;")
1457-
result.append("}")
1463+
for i in range(2 if is_class_db else 1):
1464+
if is_class_db:
1465+
if i == 0:
1466+
result.append("#define CLASSDB_GET_SINGLETON_DEBUG()")
1467+
else:
1468+
result.append("#define CLASSDB_GET_SINGLETON()")
1469+
1470+
result.append(f"{class_name} *{class_name}::get_singleton() {{")
1471+
result.append(f"\tconst StringName __class_name = {class_name}::get_class_static();")
1472+
result.append(
1473+
"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(__class_name._native_ptr());",
1474+
)
1475+
if not is_class_db:
1476+
result.append("#ifdef DEBUG_ENABLED")
1477+
if i == 0:
1478+
result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);")
1479+
if not is_class_db:
1480+
result.append("#endif // DEBUG_ENABLED")
1481+
1482+
result.append(
1483+
f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::___binding_callbacks));",
1484+
)
1485+
result.append("\treturn singleton;")
1486+
result.append("}")
1487+
1488+
if is_class_db:
1489+
result.append(
1490+
get_class_db_macro_end_mark_line(
1491+
"CLASSDB_GET_SINGLETON_DEBUG" if i == 0 else "CLASSDB_GET_SINGLETON"
1492+
)
1493+
)
1494+
result.append("")
1495+
14581496
result.append("")
14591497

1498+
if is_class_db:
1499+
result.append("#define CLASSDB_SOURCE_IMPLEMENT()")
14601500
if "methods" in class_api:
14611501
for method in class_api["methods"]:
14621502
if method["is_virtual"]:
@@ -1569,9 +1609,15 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
15691609
result.append(method_signature)
15701610
result.append("")
15711611

1612+
if is_class_db:
1613+
result.append(get_class_db_macro_end_mark_line("CLASSDB_SOURCE_IMPLEMENT"))
1614+
15721615
result.append("")
15731616
result.append("} // namespace godot ")
15741617

1618+
if is_class_db:
1619+
append_backslash_to_class_db_macro(result)
1620+
15751621
return "\n".join(result)
15761622

15771623

@@ -2275,6 +2321,7 @@ def escape_identifier(id):
22752321
"operator": "_operator",
22762322
"typeof": "type_of",
22772323
"typename": "type_name",
2324+
"enum": "_enum",
22782325
}
22792326
if id in cpp_keywords_map:
22802327
return cpp_keywords_map[id]
@@ -2376,3 +2423,60 @@ def add_header(filename, lines):
23762423

23772424
lines.append("// THIS FILE IS GENERATED. EDITS WILL BE LOST.")
23782425
lines.append("")
2426+
2427+
2428+
def calculate_line_length_with_table(line, tab_len):
2429+
tabs_len = 0
2430+
for c in line:
2431+
if c == "\t":
2432+
tabs_len += tab_len - 1
2433+
return len(line) + tabs_len
2434+
2435+
2436+
def add_backslash(line, backslash_pos, tab_len):
2437+
for i in range(calculate_line_length_with_table(line, tab_len), backslash_pos):
2438+
line += " "
2439+
return line + "\\"
2440+
2441+
2442+
def get_class_db_macro_end_mark():
2443+
return "// Macro end mark: "
2444+
2445+
2446+
def get_class_db_macro_end_mark_line(macros_name):
2447+
return get_class_db_macro_end_mark() + macros_name
2448+
2449+
2450+
def append_backslash_to_class_db_macro(lines, tab_len=4):
2451+
for i in range(len(lines)):
2452+
if lines[i].startswith("#define CLASSDB_"):
2453+
# Find finish mark.
2454+
finish_mark_line = -1
2455+
for j in range(i, len(lines)):
2456+
if lines[j].startswith(get_class_db_macro_end_mark()):
2457+
finish_mark_line = j
2458+
break
2459+
if finish_mark_line - i < 2:
2460+
continue
2461+
2462+
# Find backslash slash end line.
2463+
backslash_end_line = finish_mark_line - 1
2464+
for j in range(backslash_end_line, i, -1):
2465+
if lines[j] != "":
2466+
backslash_end_line = j
2467+
break
2468+
2469+
# Find the max length of code block.
2470+
backslash_pos = 0
2471+
for j in range(i, backslash_end_line):
2472+
length = calculate_line_length_with_table(lines[j], tab_len)
2473+
if length > backslash_pos:
2474+
backslash_pos = length
2475+
backslash_pos += tab_len - (backslash_pos % tab_len)
2476+
2477+
# Add backslash.
2478+
for j in range(i, backslash_end_line):
2479+
lines[j] = add_backslash(lines[j], backslash_pos, tab_len)
2480+
2481+
# Skip to finish mark line.
2482+
i = finish_mark_line

include/godot_cpp/core/class_db.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#include <unordered_map>
4545
#include <vector>
4646

47+
#include <godot_cpp/classes/class_db.hpp>
48+
4749
// Needed to use StringName as key in `std::unordered_map`
4850
template <>
4951
struct std::hash<godot::StringName> {
@@ -73,7 +75,9 @@ MethodDefinition D_METHOD(StringName p_name, StringName p_arg1, Args... args) {
7375
return md;
7476
}
7577

76-
class ClassDB {
78+
class ClassDB : public Object {
79+
CLASSDB_HEADER_DECLARE()
80+
private:
7781
static GDExtensionInitializationLevel current_level;
7882

7983
friend class godot::GDExtensionBinding;
@@ -145,6 +149,7 @@ class ClassDB {
145149
static void initialize(GDExtensionInitializationLevel p_level);
146150
static void deinitialize(GDExtensionInitializationLevel p_level);
147151
};
152+
CLASSDB_HEADER_ENUM()
148153

149154
#define BIND_CONSTANT(m_constant) \
150155
godot::ClassDB::bind_integer_constant(get_class_static(), "", #m_constant, m_constant);

src/core/class_db.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,20 @@
3535

3636
#include <godot_cpp/core/memory.hpp>
3737

38+
#include <godot_cpp/core/engine_ptrcall.hpp>
39+
3840
#include <algorithm>
3941

4042
namespace godot {
4143

44+
#ifdef DEBUG_ENABLED
45+
CLASSDB_GET_SINGLETON_DEBUG()
46+
#else
47+
CLASSDB_GET_SINGLETON()
48+
#endif
49+
50+
CLASSDB_SOURCE_IMPLEMENT()
51+
4252
std::unordered_map<StringName, ClassDB::ClassInfo> ClassDB::classes;
4353
std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
4454
GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;

0 commit comments

Comments
 (0)