summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Snopek <dsnopek@gmail.com>2024-05-12 08:04:57 -0500
committerGitHub <noreply@github.com>2024-05-12 08:04:57 -0500
commit798fbab653eba365664dbc77d006980c96937efd (patch)
tree61c4a3c454986d3723a095ff46f91f0f3eecafb0
parent85172dad1bd3cfed8cf8b4c2826f34cfe211af05 (diff)
parent88df025aa0ab92ed176f91dd835c004fa26f0ed0 (diff)
downloadredot-cpp-798fbab653eba365664dbc77d006980c96937efd.tar.gz
Merge pull request #1458 from dsnopek/free-callback-crash
Clean up instance bindings for engine singletons to prevent crash
-rw-r--r--binding_generator.py24
-rw-r--r--include/godot_cpp/core/class_db.hpp18
-rw-r--r--include/godot_cpp/godot.hpp1
-rw-r--r--src/core/class_db.cpp18
-rw-r--r--src/godot.cpp2
-rw-r--r--test/project/main.gd3
-rw-r--r--test/src/example.cpp7
-rw-r--r--test/src/example.h2
8 files changed, 74 insertions, 1 deletions
diff --git a/binding_generator.py b/binding_generator.py
index b973d9f..71d9e6b 100644
--- a/binding_generator.py
+++ b/binding_generator.py
@@ -1506,6 +1506,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append(f"\tGDEXTENSION_CLASS({class_name}, {inherits})")
result.append("")
+ if is_singleton:
+ result.append(f"\tstatic {class_name} *singleton;")
+ result.append("")
+
result.append("public:")
result.append("")
@@ -1586,6 +1590,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us
result.append("\t}")
result.append("")
+
+ if is_singleton:
+ result.append(f"\t~{class_name}();")
+ result.append("")
+
result.append("public:")
# Special cases.
@@ -1735,6 +1744,7 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
result.append(f"#include <godot_cpp/classes/{snake_class_name}.hpp>")
result.append("")
+ result.append("#include <godot_cpp/core/class_db.hpp>")
result.append("#include <godot_cpp/core/engine_ptrcall.hpp>")
result.append("#include <godot_cpp/core/error_macros.hpp>")
result.append("")
@@ -1749,9 +1759,10 @@ 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}::singleton = nullptr;")
+ result.append("")
result.append(f"{class_name} *{class_name}::get_singleton() {{")
# We assume multi-threaded access is OK because each assignment will assign the same value every time
- result.append(f"\tstatic {class_name} *singleton = nullptr;")
result.append("\tif (unlikely(singleton == nullptr)) {")
result.append(
f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());"
@@ -1765,11 +1776,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
result.append("#ifdef DEBUG_ENABLED")
result.append("\t\tERR_FAIL_NULL_V(singleton, nullptr);")
result.append("#endif // DEBUG_ENABLED")
+ result.append("\t\tif (likely(singleton)) {")
+ result.append(f"\t\t\tClassDB::_register_engine_singleton({class_name}::get_class_static(), singleton);")
+ result.append("\t\t}")
result.append("\t}")
result.append("\treturn singleton;")
result.append("}")
result.append("")
+ result.append(f"{class_name}::~{class_name}() {{")
+ result.append("\tif (singleton == this) {")
+ result.append(f"\t\tClassDB::_unregister_engine_singleton({class_name}::get_class_static());")
+ result.append("\t\tsingleton = nullptr;")
+ result.append("\t}")
+ result.append("}")
+ result.append("")
+
if "methods" in class_api:
for method in class_api["methods"]:
if method["is_virtual"]:
diff --git a/include/godot_cpp/core/class_db.hpp b/include/godot_cpp/core/class_db.hpp
index ca5a326..8ef7f8e 100644
--- a/include/godot_cpp/core/class_db.hpp
+++ b/include/godot_cpp/core/class_db.hpp
@@ -45,6 +45,7 @@
#include <godot_cpp/variant/callable_method_pointer.hpp>
#include <list>
+#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
@@ -104,6 +105,8 @@ private:
static std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> instance_binding_callbacks;
// Used to remember the custom class registration order.
static std::vector<StringName> class_register_order;
+ static std::unordered_map<StringName, Object *> engine_singletons;
+ static std::mutex engine_singletons_mutex;
static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const void **p_defs, int p_defcount);
static void initialize_class(const ClassInfo &cl);
@@ -153,6 +156,21 @@ public:
instance_binding_callbacks[p_name] = p_callbacks;
}
+ static void _register_engine_singleton(const StringName &p_class_name, Object *p_singleton) {
+ std::lock_guard<std::mutex> lock(engine_singletons_mutex);
+ std::unordered_map<StringName, Object *>::const_iterator i = engine_singletons.find(p_class_name);
+ if (i != engine_singletons.end()) {
+ ERR_FAIL_COND((*i).second != p_singleton);
+ return;
+ }
+ engine_singletons[p_class_name] = p_singleton;
+ }
+
+ static void _unregister_engine_singleton(const StringName &p_class_name) {
+ std::lock_guard<std::mutex> lock(engine_singletons_mutex);
+ engine_singletons.erase(p_class_name);
+ }
+
template <typename N, typename M, typename... VarArgs>
static MethodBind *bind_method(N p_method_name, M p_method, VarArgs... p_args);
diff --git a/include/godot_cpp/godot.hpp b/include/godot_cpp/godot.hpp
index 95a0387..822363d 100644
--- a/include/godot_cpp/godot.hpp
+++ b/include/godot_cpp/godot.hpp
@@ -162,6 +162,7 @@ extern "C" GDExtensionInterfaceObjectDestroy gdextension_interface_object_destro
extern "C" GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton;
extern "C" GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding;
extern "C" GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding;
+extern "C" GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding;
extern "C" GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance;
extern "C" GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name;
extern "C" GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to;
diff --git a/src/core/class_db.cpp b/src/core/class_db.cpp
index acead8b..d9a8bf9 100644
--- a/src/core/class_db.cpp
+++ b/src/core/class_db.cpp
@@ -43,6 +43,8 @@ namespace godot {
std::unordered_map<StringName, ClassDB::ClassInfo> ClassDB::classes;
std::unordered_map<StringName, const GDExtensionInstanceBindingCallbacks *> ClassDB::instance_binding_callbacks;
std::vector<StringName> ClassDB::class_register_order;
+std::unordered_map<StringName, Object *> ClassDB::engine_singletons;
+std::mutex ClassDB::engine_singletons_mutex;
GDExtensionInitializationLevel ClassDB::current_level = GDEXTENSION_INITIALIZATION_CORE;
MethodDefinition D_METHOD(StringName p_name) {
@@ -419,6 +421,22 @@ void ClassDB::deinitialize(GDExtensionInitializationLevel p_level) {
});
class_register_order.erase(it, class_register_order.end());
}
+
+ if (p_level == GDEXTENSION_INITIALIZATION_CORE) {
+ // Make a new list of the singleton objects, since freeing the instance bindings will lead to
+ // elements getting removed from engine_singletons.
+ std::vector<Object *> singleton_objects;
+ {
+ std::lock_guard<std::mutex> lock(engine_singletons_mutex);
+ singleton_objects.reserve(engine_singletons.size());
+ for (const std::pair<StringName, Object *> &pair : engine_singletons) {
+ singleton_objects.push_back(pair.second);
+ }
+ }
+ for (std::vector<Object *>::iterator i = singleton_objects.begin(); i != singleton_objects.end(); i++) {
+ internal::gdextension_interface_object_free_instance_binding((*i)->_owner, internal::token);
+ }
+ }
}
} // namespace godot
diff --git a/src/godot.cpp b/src/godot.cpp
index ebefc97..a5cefbf 100644
--- a/src/godot.cpp
+++ b/src/godot.cpp
@@ -168,6 +168,7 @@ GDExtensionInterfaceObjectDestroy gdextension_interface_object_destroy = nullptr
GDExtensionInterfaceGlobalGetSingleton gdextension_interface_global_get_singleton = nullptr;
GDExtensionInterfaceObjectGetInstanceBinding gdextension_interface_object_get_instance_binding = nullptr;
GDExtensionInterfaceObjectSetInstanceBinding gdextension_interface_object_set_instance_binding = nullptr;
+GDExtensionInterfaceObjectFreeInstanceBinding gdextension_interface_object_free_instance_binding = nullptr;
GDExtensionInterfaceObjectSetInstance gdextension_interface_object_set_instance = nullptr;
GDExtensionInterfaceObjectGetClassName gdextension_interface_object_get_class_name = nullptr;
GDExtensionInterfaceObjectCastTo gdextension_interface_object_cast_to = nullptr;
@@ -442,6 +443,7 @@ GDExtensionBool GDExtensionBinding::init(GDExtensionInterfaceGetProcAddress p_ge
LOAD_PROC_ADDRESS(global_get_singleton, GDExtensionInterfaceGlobalGetSingleton);
LOAD_PROC_ADDRESS(object_get_instance_binding, GDExtensionInterfaceObjectGetInstanceBinding);
LOAD_PROC_ADDRESS(object_set_instance_binding, GDExtensionInterfaceObjectSetInstanceBinding);
+ LOAD_PROC_ADDRESS(object_free_instance_binding, GDExtensionInterfaceObjectFreeInstanceBinding);
LOAD_PROC_ADDRESS(object_set_instance, GDExtensionInterfaceObjectSetInstance);
LOAD_PROC_ADDRESS(object_get_class_name, GDExtensionInterfaceObjectGetClassName);
LOAD_PROC_ADDRESS(object_cast_to, GDExtensionInterfaceObjectCastTo);
diff --git a/test/project/main.gd b/test/project/main.gd
index 0cfba19..665582f 100644
--- a/test/project/main.gd
+++ b/test/project/main.gd
@@ -256,6 +256,9 @@ func _ready():
assert_equal(example.test_virtual_implemented_in_script("Virtual", 939), "Implemented")
assert_equal(custom_signal_emitted, ["Virtual", 939])
+ # Test that we can access an engine singleton.
+ assert_equal(example.test_use_engine_singleton(), OS.get_name())
+
# Test that notifications happen on both parent and child classes.
var example_child = $ExampleChild
assert_equal(example_child.get_value1(), 11)
diff --git a/test/src/example.cpp b/test/src/example.cpp
index 3ec8bca..78d7062 100644
--- a/test/src/example.cpp
+++ b/test/src/example.cpp
@@ -11,6 +11,7 @@
#include <godot_cpp/classes/label.hpp>
#include <godot_cpp/classes/multiplayer_api.hpp>
#include <godot_cpp/classes/multiplayer_peer.hpp>
+#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
@@ -239,6 +240,8 @@ void Example::_bind_methods() {
GDVIRTUAL_BIND(_do_something_virtual, "name", "value");
ClassDB::bind_method(D_METHOD("test_virtual_implemented_in_script"), &Example::test_virtual_implemented_in_script);
+ ClassDB::bind_method(D_METHOD("test_use_engine_singleton"), &Example::test_use_engine_singleton);
+
ClassDB::bind_static_method("Example", D_METHOD("test_static", "a", "b"), &Example::test_static);
ClassDB::bind_static_method("Example", D_METHOD("test_static2"), &Example::test_static2);
@@ -671,6 +674,10 @@ String Example::test_virtual_implemented_in_script(const String &p_name, int p_v
return "Unimplemented";
}
+String Example::test_use_engine_singleton() const {
+ return OS::get_singleton()->get_name();
+}
+
void ExampleRuntime::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_prop_value", "value"), &ExampleRuntime::set_prop_value);
ClassDB::bind_method(D_METHOD("get_prop_value"), &ExampleRuntime::get_prop_value);
diff --git a/test/src/example.h b/test/src/example.h
index 0ad9493..1af4e5f 100644
--- a/test/src/example.h
+++ b/test/src/example.h
@@ -186,6 +186,8 @@ public:
GDVIRTUAL2R(String, _do_something_virtual, String, int);
String test_virtual_implemented_in_script(const String &p_name, int p_value);
+
+ String test_use_engine_singleton() const;
};
VARIANT_ENUM_CAST(Example::Constants);