summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuslan Mustakov <r.mustakov@gmail.com>2017-07-26 15:58:12 +0700
committerRuslan Mustakov <r.mustakov@gmail.com>2017-07-26 19:39:10 +0700
commit7f32023a1ac60b62bd0159a542c25fdad0864dba (patch)
tree8d0bf427ab1c4aa1839479cfe881f6fdca38ef8a
parentf55211ae0dc202cc015c247495af8e05af81b24b (diff)
Support multithreading for NativeScriptLanguage
Godot may call property setters from non-main thread when an object is loaded in the edtior. This means NativeScriptLanguage could be accessed from different threads, but it was not designed for thread-safety. Besides, previous behaviour made it so that godot_nativescript_init and godot_gdnative_init could be invoked from non-main thread, while godot_gdnative_thread is always invoked on the main thread. This may not be expected by the binding library. This commit defers native library initialization to the main thread and adds godot_nativescript_thread_enter and godot_nativescript_thread_exit callbacks to make a binding library aware of foreign threads.
-rw-r--r--modules/nativescript/nativescript.cpp192
-rw-r--r--modules/nativescript/nativescript.h28
-rw-r--r--modules/nativescript/register_types.cpp29
3 files changed, 211 insertions, 38 deletions
diff --git a/modules/nativescript/nativescript.cpp b/modules/nativescript/nativescript.cpp
index fd83b74727..f3db33cce2 100644
--- a/modules/nativescript/nativescript.cpp
+++ b/modules/nativescript/nativescript.cpp
@@ -40,6 +40,10 @@
#include "scene/main/scene_tree.h"
#include "scene/resources/scene_format_text.h"
+#ifndef NO_THREADS
+#include "os/thread.h"
+#endif
+
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
#include "api_generator.h"
#endif
@@ -106,42 +110,16 @@ void NativeScript::set_library(Ref<GDNativeLibrary> p_library) {
return;
}
library = p_library;
-
- // See if this library was "registered" already.
-
lib_path = library->get_active_library_path();
- Map<String, Ref<GDNative> >::Element *E = NSL->library_gdnatives.find(lib_path);
-
- if (!E) {
- Ref<GDNative> gdn;
- gdn.instance();
- gdn->set_library(library);
-
- // TODO(karroffel): check the return value?
- gdn->initialize();
-
- NSL->library_gdnatives.insert(lib_path, gdn);
- NSL->library_classes.insert(lib_path, Map<StringName, NativeScriptDesc>());
-
- if (!NSL->library_script_users.has(lib_path))
- NSL->library_script_users.insert(lib_path, Set<NativeScript *>());
-
- NSL->library_script_users[lib_path].insert(this);
-
- void *args[1] = {
- (void *)&lib_path
- };
-
- // here the library registers all the classes and stuff.
- gdn->call_native_raw(NSL->_init_call_type,
- NSL->_init_call_name,
- NULL,
- 1,
- args,
- NULL);
- } else {
- // already initialized. Nice.
+#ifndef NO_THREADS
+ if (Thread::get_caller_ID() != Thread::get_main_ID()) {
+ NSL->defer_init_library(p_library, this);
+ } else
+#endif
+ {
+ NSL->init_library(p_library);
+ NSL->register_script(this);
}
}
@@ -445,7 +423,7 @@ NativeScript::NativeScript() {
// TODO(karroffel): implement this
NativeScript::~NativeScript() {
- NSL->library_script_users[lib_path].erase(this);
+ NSL->unregister_script(this);
}
////// ScriptInstance stuff
@@ -798,6 +776,9 @@ void NativeScriptLanguage::_unload_stuff() {
NativeScriptLanguage::NativeScriptLanguage() {
NativeScriptLanguage::singleton = this;
+#ifndef NO_THREADS
+ mutex = Mutex::create();
+#endif
}
// TODO(karroffel): implement this
@@ -811,6 +792,10 @@ NativeScriptLanguage::~NativeScriptLanguage() {
NSL->library_gdnatives.clear();
NSL->library_script_users.clear();
}
+
+#ifndef NO_THREADS
+ memdelete(mutex);
+#endif
}
String NativeScriptLanguage::get_name() const {
@@ -948,6 +933,134 @@ int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, in
return -1;
}
+#ifndef NO_THREADS
+void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) {
+ MutexLock lock(mutex);
+ libs_to_init.insert(lib);
+ scripts_to_register.insert(script);
+ has_objects_to_register = true;
+}
+#endif
+
+void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) {
+#ifndef NO_THREADS
+ MutexLock lock(mutex);
+#endif
+ // See if this library was "registered" already.
+ const String &lib_path = lib->get_active_library_path();
+ Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
+
+ if (!E) {
+ Ref<GDNative> gdn;
+ gdn.instance();
+ gdn->set_library(lib);
+
+ // TODO(karroffel): check the return value?
+ gdn->initialize();
+
+ library_gdnatives.insert(lib_path, gdn);
+
+ library_classes.insert(lib_path, Map<StringName, NativeScriptDesc>());
+
+ if (!library_script_users.has(lib_path))
+ library_script_users.insert(lib_path, Set<NativeScript *>());
+
+ void *args[1] = {
+ (void *)&lib_path
+ };
+
+ // here the library registers all the classes and stuff.
+ gdn->call_native_raw(_init_call_type,
+ _init_call_name,
+ NULL,
+ 1,
+ args,
+ NULL);
+ } else {
+ // already initialized. Nice.
+ }
+}
+
+void NativeScriptLanguage::register_script(NativeScript *script) {
+#ifndef NO_THREADS
+ MutexLock lock(mutex);
+#endif
+ library_script_users[script->lib_path].insert(script);
+}
+
+void NativeScriptLanguage::unregister_script(NativeScript *script) {
+#ifndef NO_THREADS
+ MutexLock lock(mutex);
+#endif
+ Map<String, Set<NativeScript *> >::Element *S = library_script_users.find(script->lib_path);
+ if (S) {
+ S->get().erase(script);
+ if (S->get().size() == 0) {
+ library_script_users.erase(S);
+ }
+ }
+#ifndef NO_THREADS
+ scripts_to_register.erase(script);
+#endif
+}
+
+#ifndef NO_THREADS
+
+void NativeScriptLanguage::frame() {
+ if (has_objects_to_register) {
+ MutexLock lock(mutex);
+ for (Set<Ref<GDNativeLibrary> >::Element *L = libs_to_init.front(); L; L = L->next()) {
+ init_library(L->get());
+ }
+ libs_to_init.clear();
+ for (Set<NativeScript *>::Element *S = scripts_to_register.front(); S; S = S->next()) {
+ register_script(S->get());
+ }
+ scripts_to_register.clear();
+ has_objects_to_register = false;
+ }
+}
+
+void NativeScriptLanguage::thread_enter() {
+ Vector<Ref<GDNative> > libs;
+ {
+ MutexLock lock(mutex);
+ for (Map<String, Ref<GDNative> >::Element *L = library_gdnatives.front(); L; L = L->next()) {
+ libs.push_back(L->get());
+ }
+ }
+ for (int i = 0; i < libs.size(); ++i) {
+ libs[i]->call_native_raw(
+ _thread_cb_call_type,
+ _thread_enter_call_name,
+ NULL,
+ 0,
+ NULL,
+ NULL);
+ }
+}
+
+void NativeScriptLanguage::thread_exit() {
+ Vector<Ref<GDNative> > libs;
+ {
+ MutexLock lock(mutex);
+ for (Map<String, Ref<GDNative> >::Element *L = library_gdnatives.front(); L; L = L->next()) {
+ libs.push_back(L->get());
+ }
+ }
+ for (int i = 0; i < libs.size(); ++i) {
+ libs[i]->call_native_raw(
+ _thread_cb_call_type,
+ _thread_exit_call_name,
+ NULL,
+ 0,
+ NULL,
+ NULL);
+ }
+}
+
+#endif // NO_THREADS
+
void NativeReloadNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_notification"), &NativeReloadNode::_notification);
}
@@ -960,7 +1073,9 @@ void NativeReloadNode::_notification(int p_what) {
if (unloaded)
break;
-
+#ifndef NO_THREADS
+ MutexLock lock(NSL->mutex);
+#endif
NSL->_unload_stuff();
for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) {
@@ -976,9 +1091,10 @@ void NativeReloadNode::_notification(int p_what) {
if (!unloaded)
break;
-
+#ifndef NO_THREADS
+ MutexLock lock(NSL->mutex);
+#endif
Set<StringName> libs_to_remove;
-
for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) {
if (!L->get()->initialize()) {
diff --git a/modules/nativescript/nativescript.h b/modules/nativescript/nativescript.h
index bc7a6e3ed6..cf3a64e9b8 100644
--- a/modules/nativescript/nativescript.h
+++ b/modules/nativescript/nativescript.h
@@ -41,6 +41,10 @@
#include "godot_nativescript.h"
#include "modules/gdnative/gdnative.h"
+#ifndef NO_THREADS
+#include "os/mutex.h"
+#endif
+
struct NativeScriptDesc {
struct Method {
@@ -197,6 +201,19 @@ private:
void _unload_stuff();
+#ifndef NO_THREADS
+ Mutex *mutex;
+
+ Set<Ref<GDNativeLibrary> > libs_to_init;
+ Set<NativeScript *> scripts_to_register;
+ volatile bool has_objects_to_register; // so that we don't lock mutex every frame - it's rarely needed
+ void defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script);
+#endif
+
+ void init_library(const Ref<GDNativeLibrary> &lib);
+ void register_script(NativeScript *script);
+ void unregister_script(NativeScript *script);
+
public:
Map<String, Map<StringName, NativeScriptDesc> > library_classes;
Map<String, Ref<GDNative> > library_gdnatives;
@@ -206,6 +223,10 @@ public:
const StringName _init_call_type = "nativescript_init";
const StringName _init_call_name = "godot_nativescript_init";
+ const StringName _thread_cb_call_type = "godot_nativescript_thread_cb";
+ const StringName _thread_enter_call_name = "godot_nativescript_thread_enter";
+ const StringName _thread_exit_call_name = "godot_nativescript_thread_exit";
+
NativeScriptLanguage();
~NativeScriptLanguage();
@@ -215,6 +236,13 @@ public:
void _hacky_api_anchor();
+#ifndef NO_THREADS
+ virtual void thread_enter();
+ virtual void thread_exit();
+
+ virtual void frame();
+#endif
+
virtual String get_name() const;
virtual void init();
virtual String get_type() const;
diff --git a/modules/nativescript/register_types.cpp b/modules/nativescript/register_types.cpp
index 6c88b04a56..a8a931343b 100644
--- a/modules/nativescript/register_types.cpp
+++ b/modules/nativescript/register_types.cpp
@@ -61,6 +61,32 @@ void init_call_cb(void *p_handle, godot_string *p_proc_name, void *p_data, int p
fn(args[0]);
}
+#ifndef NO_THREADS
+
+typedef void (*native_script_empty_callback)();
+
+void thread_call_cb(void *p_handle, godot_string *p_proc_name, void *p_data, int p_num_args, void **args, void *r_ret) {
+ if (p_handle == NULL) {
+ ERR_PRINT("No valid library handle, can't call nativescript thread enter/exit callback");
+ return;
+ }
+
+ void *library_proc;
+ Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(
+ p_handle,
+ *(String *)p_proc_name,
+ library_proc);
+ if (err != OK) {
+ // it's fine if thread callbacks are not present in the library.
+ return;
+ }
+
+ native_script_empty_callback fn = (native_script_empty_callback)library_proc;
+ fn();
+}
+
+#endif // NO_THREADS
+
ResourceFormatLoaderNativeScript *resource_loader_gdns = NULL;
ResourceFormatSaverNativeScript *resource_saver_gdns = NULL;
@@ -72,6 +98,9 @@ void register_nativescript_types() {
ScriptServer::register_language(native_script_language);
GDNativeCallRegistry::singleton->register_native_raw_call_type(native_script_language->_init_call_type, init_call_cb);
+#ifndef NO_THREADS
+ GDNativeCallRegistry::singleton->register_native_raw_call_type(native_script_language->_thread_cb_call_type, thread_call_cb);
+#endif
resource_saver_gdns = memnew(ResourceFormatSaverNativeScript);
ResourceSaver::add_resource_format_saver(resource_saver_gdns);