Merge pull request #9867 from endragor/nativescript-threading
Support multithreading for NativeScriptLanguage
This commit is contained in:
commit
2b99bd492b
3 changed files with 211 additions and 38 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue