Skip to content

Commit 8edf073

Browse files
committed
Clean up instance bindings for engine singletons to prevent crash
1 parent 54fe2f9 commit 8edf073

File tree

8 files changed

+74
-1
lines changed

8 files changed

+74
-1
lines changed

binding_generator.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
15041504
result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
15051505
result.append("")
15061506

1507+
if is_singleton:
1508+
result.append(f"\tstatic {class_name} *singleton;")
1509+
result.append("")
1510+
15071511
result.append("public:")
15081512
result.append("")
15091513

@@ -1584,6 +1588,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
15841588

15851589
result.append("\t}")
15861590
result.append("")
1591+
1592+
if is_singleton:
1593+
result.append(f"\t~{class_name}();")
1594+
result.append("")
1595+
15871596
result.append("public:")
15881597

15891598
# Special cases.
@@ -1733,6 +1742,7 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
17331742

17341743
result.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
17351744
result.append("")
1745+
result.append("#include <godot_cpp/core/class_db.hpp>")
17361746
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
17371747
result.append("#include <godot_cpp/core/error_macros.hpp>")
17381748
result.append("")
@@ -1747,9 +1757,10 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
17471757
result.append("")
17481758

17491759
if is_singleton:
1760+
result.append(f"{class_name} *{class_name}::singleton = nullptr;")
1761+
result.append("")
17501762
result.append(f"{class_name} *{class_name}::get_singleton() {{")
17511763
# We assume multi-threaded access is OK because each assignment will assign the same value every time
1752-
result.append(f"\tstatic {class_name} *singleton = nullptr;")
17531764
result.append("\tif (unlikely(singleton == nullptr)) {")
17541765
result.append(
17551766
f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());"
@@ -1763,11 +1774,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
17631774
result.append("#ifdef DEBUG_ENABLED")
17641775
result.append("\t\tERR_FAIL_NULL_V(singleton, nullptr);")
17651776
result.append("#endif // DEBUG_ENABLED")
1777+
result.append("\t\tif (likely(singleton)) {")
1778+
result.append(f"\t\t\tClassDB::_register_engine_singleton({class_name}::get_class_static(), singleton);")
1779+
result.append("\t\t}")
17661780
result.append("\t}")
17671781
result.append("\treturn singleton;")
17681782
result.append("}")
17691783
result.append("")
17701784

1785+
result.append(f"{class_name}::~{class_name}() {{")
1786+
result.append("\tif (singleton == this) {")
1787+
result.append(f"\t\tClassDB::_unregister_engine_singleton({class_name}::get_class_static());")
1788+
result.append("\t\tsingleton = nullptr;")
1789+
result.append("\t}")
1790+
result.append("}")
1791+
result.append("")
1792+
17711793
if "methods" in class_api:
17721794
for method in class_api["methods"]:
17731795
if method["is_virtual"]:

include/godot_cpp/core/class_db.hpp

+18
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <godot_cpp/variant/callable_method_pointer.hpp>
4646

4747
#include <list>
48+
#include <mutex>
4849
#include <set>
4950
#include <string>
5051
#include <unordered_map>
@@ -104,6 +105,8 @@ class ClassDB {
104105
static std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> instance_binding_callbacks;
105106
// Used to remember the custom class registration order.
106107
static std::vector<StringName> class_register_order;
108+
static std::unordered_map<StringName, Object *> engine_singletons;
109+
static std::mutex engine_singletons_mutex;
107110

108111
static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount);
109112
static void initialize_class(const ClassInfo &cl);
@@ -153,6 +156,21 @@ class ClassDB {
153156
instance_binding_callbacks[p_name] = p_callbacks;
154157
}
155158

159+
static void _register_engine_singleton(const StringName &p_class_name, Object *p_singleton) {
160+
std::lock_guard<std::mutex> lock(engine_singletons_mutex);
161+
std::unordered_map<StringName, Object *>::const_iterator i = engine_singletons.find(p_class_name);
162+
if (i != engine_singletons.end()) {
163+
ERR_FAIL_COND((*i).second != p_singleton);
164+
return;
165+
}
166+
engine_singletons[p_class_name] = p_singleton;
167+
}
168+
169+
static void _unregister_engine_singleton(const StringName &p_class_name) {
170+
std::lock_guard<std::mutex> lock(engine_singletons_mutex);
171+
engine_singletons.erase(p_class_name);
172+
}
173+
156174
template <typename N, typename M, typename... VarArgs>
157175
static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args);
158176

include/godot_cpp/godot.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ extern "C" GDExtensionInterfaceObjectDestroy gdextension_interface_object_destro
160160
extern "C" GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton;
161161
extern "C" GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding;
162162
extern "C" GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding;
163+
extern "C" GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding;
163164
extern "C" GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance;
164165
extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name;
165166
extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to;

src/core/class_db.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ namespace godot {
4343
std::unordered_map<StringName, ClassDB::ClassInfo> ClassDB::classes;
4444
std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
4545
std::vector<StringName> ClassDB::class_register_order;
46+
std::unordered_map<StringName, Object *> ClassDB::engine_singletons;
47+
std::mutex ClassDB::engine_singletons_mutex;
4648
GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;
4749

4850
MethodDefinition D_METHOD(StringName p_name) {
@@ -419,6 +421,22 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
419421
});
420422
class_register_order.erase(it, class_register_order.end());
421423
}
424+
425+
if (p_level == GDEXTENSION_INITIALIZATION_CORE) {
426+
// Make a new list of the singleton objects, since freeing the instance bindings will lead to
427+
// elements getting removed from engine_singletons.
428+
std::vector<Object *> singleton_objects;
429+
{
430+
std::lock_guard<std::mutex> lock(engine_singletons_mutex);
431+
singleton_objects.reserve(engine_singletons.size());
432+
for (const std::pair<StringName, Object *> &pair : engine_singletons) {
433+
singleton_objects.push_back(pair.second);
434+
}
435+
}
436+
for (std::vector<Object *>::iterator i = singleton_objects.begin(); i != singleton_objects.end(); i++) {
437+
internal::gdextension_interface_object_free_instance_binding((*i)->_owner, internal::token);
438+
}
439+
}
422440
}
423441

424442
} // namespace godot

src/godot.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ GDExtensionInterfaceObjectDestroy gdextension_interface_object_destroy = nullptr
166166
GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton = nullptr;
167167
GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding = nullptr;
168168
GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding = nullptr;
169+
GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding = nullptr;
169170
GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance = nullptr;
170171
GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name = nullptr;
171172
GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr;
@@ -406,6 +407,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
406407
LOAD_PROC_ADDRESS(global_get_singleton, GDExtensionInterfaceGlobalGetSingleton);
407408
LOAD_PROC_ADDRESS(object_get_instance_binding, GDExtensionInterfaceObjectGetInstanceBinding);
408409
LOAD_PROC_ADDRESS(object_set_instance_binding, GDExtensionInterfaceObjectSetInstanceBinding);
410+
LOAD_PROC_ADDRESS(object_free_instance_binding, GDExtensionInterfaceObjectFreeInstanceBinding);
409411
LOAD_PROC_ADDRESS(object_set_instance, GDExtensionInterfaceObjectSetInstance);
410412
LOAD_PROC_ADDRESS(object_get_class_name, GDExtensionInterfaceObjectGetClassName);
411413
LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo);

test/project/main.gd

+3
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ func _ready():
256256
assert_equal(example.test_virtual_implemented_in_script("Virtual", 939), "Implemented")
257257
assert_equal(custom_signal_emitted, ["Virtual", 939])
258258

259+
# Test that we can access an engine singleton.
260+
assert_equal(example.test_use_engine_singleton(), OS.get_name())
261+
259262
# Test that notifications happen on both parent and child classes.
260263
var example_child = $ExampleChild
261264
assert_equal(example_child.get_value1(), 11)

test/src/example.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <godot_cpp/classes/label.hpp>
1212
#include <godot_cpp/classes/multiplayer_api.hpp>
1313
#include <godot_cpp/classes/multiplayer_peer.hpp>
14+
#include <godot_cpp/classes/os.hpp>
1415
#include <godot_cpp/variant/utility_functions.hpp>
1516

1617
using namespace godot;
@@ -239,6 +240,8 @@ void Example::_bind_methods() {
239240
GDVIRTUAL_BIND(_do_something_virtual, "name", "value");
240241
ClassDB::bind_method(D_METHOD("test_virtual_implemented_in_script"), &Example::test_virtual_implemented_in_script);
241242

243+
ClassDB::bind_method(D_METHOD("test_use_engine_singleton"), &Example::test_use_engine_singleton);
244+
242245
ClassDB::bind_static_method("Example", D_METHOD("test_static", "a", "b"), &Example::test_static);
243246
ClassDB::bind_static_method("Example", D_METHOD("test_static2"), &Example::test_static2);
244247

@@ -671,6 +674,10 @@ String Example::test_virtual_implemented_in_script(const String &p_name, int p_v
671674
return "Unimplemented";
672675
}
673676

677+
String Example::test_use_engine_singleton() const {
678+
return OS::get_singleton()->get_name();
679+
}
680+
674681
void ExampleRuntime::_bind_methods() {
675682
ClassDB::bind_method(D_METHOD("set_prop_value", "value"), &ExampleRuntime::set_prop_value);
676683
ClassDB::bind_method(D_METHOD("get_prop_value"), &ExampleRuntime::get_prop_value);

test/src/example.h

+2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ class Example : public Control {
186186

187187
GDVIRTUAL2R(String, _do_something_virtual, String, int);
188188
String test_virtual_implemented_in_script(const String &p_name, int p_value);
189+
190+
String test_use_engine_singleton() const;
189191
};
190192

191193
VARIANT_ENUM_CAST(Example::Constants);

0 commit comments

Comments
 (0)