C#: Improve tool script support and fix reloading issues

This commit is contained in:
Ignacio Etcheverry 2018-11-30 20:43:06 +01:00
parent bf94eed60c
commit b9b7dcdf00
12 changed files with 550 additions and 351 deletions

View file

@ -103,6 +103,16 @@ import os
def find_nuget_unix(): def find_nuget_unix():
import os
if 'NUGET_PATH' in os.environ:
hint_path = os.environ['NUGET_PATH']
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
hint_path = os.path.join(hint_path, 'nuget')
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
import os.path import os.path
import sys import sys
@ -129,6 +139,16 @@ def find_nuget_unix():
def find_nuget_windows(): def find_nuget_windows():
import os
if 'NUGET_PATH' in os.environ:
hint_path = os.environ['NUGET_PATH']
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
hint_path = os.path.join(hint_path, 'nuget.exe')
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
import mono_reg_utils as monoreg import mono_reg_utils as monoreg
mono_root = '' mono_root = ''
@ -160,14 +180,6 @@ def find_nuget_windows():
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path return hint_path
if 'NUGET_PATH' in os.environ:
hint_path = os.environ['NUGET_PATH']
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
hint_path = os.path.join(hint_path, 'nuget.exe')
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
return None return None

View file

@ -50,6 +50,7 @@
#include "mono_gd/gd_mono_marshal.h" #include "mono_gd/gd_mono_marshal.h"
#include "signal_awaiter_utils.h" #include "signal_awaiter_utils.h"
#include "utils/macros.h" #include "utils/macros.h"
#include "utils/mutex_utils.h"
#include "utils/string_utils.h" #include "utils/string_utils.h"
#include "utils/thread_local.h" #include "utils/thread_local.h"
@ -378,60 +379,72 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
return "object"; return "object";
if (!ClassDB::class_exists(p_var_type_name)) { if (!ClassDB::class_exists(p_var_type_name)) {
Variant::Type var_types[] = { return p_var_type_name;
Variant::BOOL,
Variant::INT,
Variant::REAL,
Variant::STRING,
Variant::VECTOR2,
Variant::RECT2,
Variant::VECTOR3,
Variant::TRANSFORM2D,
Variant::PLANE,
Variant::QUAT,
Variant::AABB,
Variant::BASIS,
Variant::TRANSFORM,
Variant::COLOR,
Variant::NODE_PATH,
Variant::_RID
};
for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
if (p_var_type_name == Variant::get_type_name(var_types[i]))
return p_var_type_name;
}
if (p_var_type_name == "String")
return "string"; // I prefer this one >:[
// TODO these will be rewritten later into custom containers
if (p_var_type_name == "Array")
return "object[]";
if (p_var_type_name == "Dictionary")
return "Dictionary<object, object>";
if (p_var_type_name == "PoolByteArray")
return "byte[]";
if (p_var_type_name == "PoolIntArray")
return "int[]";
if (p_var_type_name == "PoolRealArray")
return "float[]";
if (p_var_type_name == "PoolStringArray")
return "string[]";
if (p_var_type_name == "PoolVector2Array")
return "Vector2[]";
if (p_var_type_name == "PoolVector3Array")
return "Vector3[]";
if (p_var_type_name == "PoolColorArray")
return "Color[]";
return "object";
} }
return p_var_type_name; if (p_var_type_name == Variant::get_type_name(Variant::OBJECT))
return "Godot.Object";
if (p_var_type_name == Variant::get_type_name(Variant::REAL)) {
#ifdef REAL_T_IS_DOUBLE
return "double";
#else
return "float";
#endif
}
if (p_var_type_name == Variant::get_type_name(Variant::STRING))
return "string"; // I prefer this one >:[
if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY))
return "Collections.Dictionary";
if (p_var_type_name == Variant::get_type_name(Variant::ARRAY))
return "Collections.Array";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY))
return "byte[]";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY))
return "int[]";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_REAL_ARRAY)) {
#ifdef REAL_T_IS_DOUBLE
return "double[]";
#else
return "float[]";
#endif
}
if (p_var_type_name == Variant::get_type_name(Variant::POOL_STRING_ARRAY))
return "string[]";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR2_ARRAY))
return "Vector2[]";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY))
return "Vector3[]";
if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY))
return "Color[]";
Variant::Type var_types[] = {
Variant::BOOL,
Variant::INT,
Variant::VECTOR2,
Variant::RECT2,
Variant::VECTOR3,
Variant::TRANSFORM2D,
Variant::PLANE,
Variant::QUAT,
Variant::AABB,
Variant::BASIS,
Variant::TRANSFORM,
Variant::COLOR,
Variant::NODE_PATH,
Variant::_RID
};
for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
if (p_var_type_name == Variant::get_type_name(var_types[i]))
return p_var_type_name;
}
return "object";
} }
String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const { String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const {
@ -507,8 +520,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames); MonoArray *frames = invoke_method_thunk(CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames), p_stack_trace, (MonoObject **)&exc);
MonoArray *frames = st_get_frames(p_stack_trace, (MonoObject **)&exc);
if (exc) { if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc); GDMonoUtils::debug_print_unhandled_exception(exc);
@ -532,7 +544,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
MonoString *file_name; MonoString *file_name;
int file_line_num; int file_line_num;
MonoString *method_decl; MonoString *method_decl;
get_sf_info(frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc); invoke_method_thunk(get_sf_info, frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc);
if (exc) { if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc); GDMonoUtils::debug_print_unhandled_exception(exc);
@ -561,10 +573,8 @@ void CSharpLanguage::frame() {
MonoObject *task_scheduler = task_scheduler_handle->get_target(); MonoObject *task_scheduler = task_scheduler_handle->get_target();
if (task_scheduler) { if (task_scheduler) {
GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
MonoException *exc = NULL; MonoException *exc = NULL;
thunk(task_scheduler, (MonoObject **)&exc); invoke_method_thunk(CACHED_METHOD_THUNK(GodotTaskScheduler, Activate), task_scheduler, (MonoObject **)&exc);
if (exc) { if (exc) {
GDMonoUtils::debug_unhandled_exception(exc); GDMonoUtils::debug_unhandled_exception(exc);
@ -599,23 +609,19 @@ void CSharpLanguage::reload_all_scripts() {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
#ifndef NO_THREADS
lock->lock();
#endif
List<Ref<CSharpScript> > scripts; List<Ref<CSharpScript> > scripts;
SelfList<CSharpScript> *elem = script_list.first(); {
while (elem) { SCOPED_MUTEX_LOCK(script_instances_mutex);
if (elem->self()->get_path().is_resource_file()) {
scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
}
#ifndef NO_THREADS SelfList<CSharpScript> *elem = script_list.first();
lock->unlock(); while (elem) {
#endif if (elem->self()->get_path().is_resource_file()) {
scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
}
}
//as scripts are going to be reloaded, must proceed without locking here //as scripts are going to be reloaded, must proceed without locking here
@ -625,6 +631,7 @@ void CSharpLanguage::reload_all_scripts() {
E->get()->load_source_code(E->get()->get_path()); E->get()->load_source_code(E->get()->get_path());
E->get()->reload(true); E->get()->reload(true);
} }
#endif #endif
} }
@ -634,15 +641,17 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
MonoReloadNode::get_singleton()->restart_reload_timer(); MonoReloadNode::get_singleton()->restart_reload_timer();
reload_assemblies_if_needed(p_soft_reload); if (is_assembly_reloading_needed()) {
reload_assemblies(p_soft_reload);
}
#endif #endif
} }
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { bool CSharpLanguage::is_assembly_reloading_needed() {
if (!gdmono->is_runtime_initialized()) if (!gdmono->is_runtime_initialized())
return; return false;
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
@ -660,164 +669,208 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
// Maybe it wasn't loaded from the default path, so check this as well // Maybe it wasn't loaded from the default path, so check this as well
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name); proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name);
if (!FileAccess::exists(proj_asm_path)) if (!FileAccess::exists(proj_asm_path))
return; // No assembly to load return false; // No assembly to load
} }
if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
return; // Already up to date return false; // Already up to date
} else { } else {
if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
return; // No assembly to load return false; // No assembly to load
} }
if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
return; // The core API assembly to load is invalidated return false; // The core API assembly to load is invalidated
if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
return; // The editor API assembly to load is invalidated return false; // The editor API assembly to load is invalidated
#ifndef NO_THREADS return true;
lock->lock(); }
#endif
void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
if (!gdmono->is_runtime_initialized())
return;
// There is no soft reloading with Mono. It's always hard reloading.
List<Ref<CSharpScript> > scripts; List<Ref<CSharpScript> > scripts;
SelfList<CSharpScript> *elem = script_list.first(); {
while (elem) { SCOPED_MUTEX_LOCK(script_instances_mutex);
if (elem->self()->get_path().is_resource_file()) {
scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to CSharpScript to avoid being erased by accident for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
if (elem->self()->get_path().is_resource_file()) {
// Cast to CSharpScript to avoid being erased by accident
scripts.push_back(Ref<CSharpScript>(elem->self()));
}
} }
elem = elem->next();
} }
#ifndef NO_THREADS List<Ref<CSharpScript> > to_reload;
lock->unlock();
#endif
//when someone asks you why dynamically typed languages are easier to write.... // As scripts are going to be reloaded, must proceed without locking here
Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
//as scripts are going to be reloaded, must proceed without locking here
scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant> > >()); Ref<CSharpScript> &script = E->get();
if (!p_soft_reload) { to_reload.push_back(script);
//save state and remove script from instances // Script::instances are deleted during managed object disposal, which happens on domain finalize.
Map<ObjectID, List<Pair<StringName, Variant> > > &map = to_reload[E->get()]; // Only placeholders are kept. Therefore we need to keep a copy before that happens.
while (E->get()->instances.front()) { for (Set<Object *>::Element *E = script->instances.front(); E; E = E->next()) {
Object *obj = E->get()->instances.front()->get(); script->pending_reload_instances.insert(E->get()->get_instance_id());
//save instance info
List<Pair<StringName, Variant> > state;
if (obj->get_script_instance()) {
obj->get_script_instance()->get_property_state(state);
Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
if (gchandle.is_valid())
gchandle->release();
map[obj->get_instance_id()] = state;
obj->set_script(RefPtr());
}
}
//same thing for placeholders
while (E->get()->placeholders.size()) {
Object *obj = E->get()->placeholders.front()->get()->get_owner();
//save instance info
List<Pair<StringName, Variant> > state;
if (obj->get_script_instance()) {
obj->get_script_instance()->get_property_state(state);
map[obj->get_instance_id()] = state;
obj->set_script(RefPtr());
} else {
// no instance found. Let's remove it so we don't loop forever
E->get()->placeholders.erase(E->get()->placeholders.front()->get());
}
}
for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) {
map[F->key()] = F->get(); //pending to reload, use this one instead
}
E->get()->_clear();
} }
#ifdef TOOLS_ENABLED
for (Set<PlaceHolderScriptInstance *>::Element *E = script->placeholders.front(); E; E = E->next()) {
script->pending_reload_instances.insert(E->get()->get_owner()->get_instance_id());
}
#endif
// FIXME: What about references? Need to keep them alive if only managed code references them.
// Save state and remove script from instances
Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
// Save instance info
CSharpScript::StateBackup state;
ERR_CONTINUE(!obj->get_script_instance());
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
obj->get_script_instance()->get_property_state(state.properties);
Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
if (gchandle.is_valid())
gchandle->release();
owners_map[obj->get_instance_id()] = state;
obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
script->_clear();
} }
// Do domain reload
if (gdmono->reload_scripts_domain() != OK) { if (gdmono->reload_scripts_domain() != OK) {
// Failed to reload the scripts domain // Failed to reload the scripts domain
// Make sure to add the scripts back to their owners before returning // Make sure to add the scripts back to their owners before returning
for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> scr = E->key(); Ref<CSharpScript> scr = E->get();
for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) {
for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) {
Object *obj = ObjectDB::get_instance(F->key()); Object *obj = ObjectDB::get_instance(F->key());
if (!obj) if (!obj)
continue; continue;
ObjectID obj_id = obj->get_instance_id();
// Use a placeholder for now to avoid losing the state when saving a scene
obj->set_script(scr.get_ref_ptr()); obj->set_script(scr.get_ref_ptr());
// Save reload state for next time if not saved
if (!scr->pending_reload_state.has(obj->get_instance_id())) { PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj);
scr->pending_reload_state[obj->get_instance_id()] = F->get(); obj->set_script_instance(placeholder);
// Even though build didn't fail, this tells the placeholder to keep properties and
// it allows using property_set_fallback for restoring the state without a valid script.
placeholder->set_build_failed(true);
// Restore Variant properties state, it will be kept by the placeholder until the next script reloading
for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
placeholder->property_set_fallback(G->get().first, G->get().second, NULL);
} }
scr->pending_reload_state.erase(obj_id);
} }
} }
return; return;
} }
for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> scr = E->key(); Ref<CSharpScript> scr = E->get();
scr->exports_invalidated = true; scr->exports_invalidated = true;
scr->signals_invalidated = true; scr->signals_invalidated = true;
scr->reload(p_soft_reload); scr->reload(p_soft_reload);
scr->update_exports(); scr->update_exports();
//restore state if saved {
for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) { #ifdef DEBUG_ENABLED
for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
Object *obj = ObjectDB::get_instance(F->key()); if (!obj) {
if (!obj) scr->pending_reload_state.erase(obj_id);
continue; continue;
if (!p_soft_reload) {
//clear it just in case (may be a pending reload state)
obj->set_script(RefPtr());
}
obj->set_script(scr.get_ref_ptr());
if (!obj->get_script_instance()) {
//failed, save reload state for next time if not saved
if (!scr->pending_reload_state.has(obj->get_instance_id())) {
scr->pending_reload_state[obj->get_instance_id()] = F->get();
} }
continue;
}
if (scr->valid && scr->is_tool() && obj->get_script_instance()->is_placeholder()) { ScriptInstance *si = obj->get_script_instance();
// Script instance was a placeholder, but now the script was built successfully and is a tool script.
// We have to replace the placeholder with an actual C# script instance.
scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(obj->get_script_instance()));
ScriptInstance *script_instance = scr->instance_create(obj);
obj->set_script_instance(script_instance); // Not necessary as it's already done in instance_create, but just in case...
}
for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) { #ifdef TOOLS_ENABLED
obj->get_script_instance()->set(G->get().first, G->get().second); if (si) {
} // If the script instance is not null, then it must be a placeholder.
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
CRASH_COND(!si->is_placeholder());
scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
// Replace placeholder with a script instance
CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
// Backup placeholder script instance state before replacing it with a script instance
obj->get_script_instance()->get_property_state(state_backup.properties);
ScriptInstance *script_instance = scr->instance_create(obj);
if (script_instance) {
scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
obj->set_script_instance(script_instance);
}
// TODO: Restore serialized state
for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
script_instance->set(G->get().first, G->get().second);
}
scr->pending_reload_state.erase(obj_id);
}
continue;
}
#else
CRASH_COND(si != NULL);
#endif
// Re-create script instance
obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
// TODO: Restore serialized state
for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
scr->pending_reload_state.erase(obj_id);
}
#endif
scr->pending_reload_instances.clear();
} }
//if instance states were saved, set them!
} }
// FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative.
if (Engine::get_singleton()->is_editor_hint()) { if (Engine::get_singleton()->is_editor_hint()) {
EditorNode::get_singleton()->get_inspector()->update_tree(); EditorNode::get_singleton()->get_inspector()->update_tree();
NodeDock::singleton->update_lists(); NodeDock::singleton->update_lists();
@ -941,26 +994,17 @@ void CSharpLanguage::set_language_index(int p_idx) {
void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) { void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
if (!p_gchandle->is_released()) { // Do not locking unnecessarily if (!p_gchandle->is_released()) { // Do not locking unnecessarily
#ifndef NO_THREADS SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
get_singleton()->script_gchandle_release_lock->lock();
#endif
p_gchandle->release(); p_gchandle->release();
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->unlock();
#endif
} }
} }
void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle) { void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_pinned_expected_obj); // we might lock after this, so pin it uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it
if (!p_gchandle->is_released()) { // Do not locking unnecessarily if (!p_gchandle->is_released()) { // Do not locking unnecessarily
#ifndef NO_THREADS SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
get_singleton()->script_gchandle_release_lock->lock();
#endif
MonoObject *target = p_gchandle->get_target(); MonoObject *target = p_gchandle->get_target();
@ -968,13 +1012,9 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj,
// already released and could have been replaced) or if we can't get its target MonoObject* // already released and could have been replaced) or if we can't get its target MonoObject*
// (which doesn't necessarily mean it was released, and we want it released in order to // (which doesn't necessarily mean it was released, and we want it released in order to
// avoid locking other threads unnecessarily). // avoid locking other threads unnecessarily).
if (target == p_pinned_expected_obj || target == NULL) { if (target == p_expected_obj || target == NULL) {
p_gchandle->release(); p_gchandle->release();
} }
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->unlock();
#endif
} }
MonoGCHandle::free_handle(pinned_gchandle); MonoGCHandle::free_handle(pinned_gchandle);
@ -990,13 +1030,13 @@ CSharpLanguage::CSharpLanguage() {
gdmono = NULL; gdmono = NULL;
#ifdef NO_THREADS #ifdef NO_THREADS
lock = NULL; script_instances_mutex = NULL;
gchandle_bind_lock = NULL; script_gchandle_release_mutex = NULL;
script_gchandle_release_lock = NULL; language_bind_mutex = NULL;
#else #else
lock = Mutex::create(); script_instances_mutex = Mutex::create();
script_bind_lock = Mutex::create(); script_gchandle_release_mutex = Mutex::create();
script_gchandle_release_lock = Mutex::create(); language_bind_mutex = Mutex::create();
#endif #endif
lang_idx = -1; lang_idx = -1;
@ -1006,19 +1046,19 @@ CSharpLanguage::~CSharpLanguage() {
finish(); finish();
if (lock) { if (script_instances_mutex) {
memdelete(lock); memdelete(script_instances_mutex);
lock = NULL; script_instances_mutex = NULL;
} }
if (script_bind_lock) { if (language_bind_mutex) {
memdelete(script_bind_lock); memdelete(language_bind_mutex);
script_bind_lock = NULL; language_bind_mutex = NULL;
} }
if (script_gchandle_release_lock) { if (script_gchandle_release_mutex) {
memdelete(script_gchandle_release_lock); memdelete(script_gchandle_release_mutex);
script_gchandle_release_lock = NULL; script_gchandle_release_mutex = NULL;
} }
singleton = NULL; singleton = NULL;
@ -1055,15 +1095,12 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
script_binding.wrapper_class = type_class; // cache script_binding.wrapper_class = type_class; // cache
script_binding.gchandle = MonoGCHandle::create_strong(mono_object); script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS void *data;
script_bind_lock->lock();
#endif
void *data = (void *)script_bindings.insert(p_object, script_binding); {
SCOPED_MUTEX_LOCK(language_bind_mutex);
#ifndef NO_THREADS data = (void *)script_bindings.insert(p_object, script_binding);
script_bind_lock->unlock(); }
#endif
// Tie managed to unmanaged // Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object); Reference *ref = Object::cast_to<Reference>(p_object);
@ -1093,23 +1130,19 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
if (finalizing) if (finalizing)
return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
#ifndef NO_THREADS {
script_bind_lock->lock(); SCOPED_MUTEX_LOCK(language_bind_mutex);
#endif
Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data; Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
// Set the native instance field to IntPtr.Zero, if not yet garbage collected // Set the native instance field to IntPtr.Zero, if not yet garbage collected
MonoObject *mono_object = data->value().gchandle->get_target(); MonoObject *mono_object = data->value().gchandle->get_target();
if (mono_object) { if (mono_object) {
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
}
script_bindings.erase(data);
} }
script_bindings.erase(data);
#ifndef NO_THREADS
script_bind_lock->unlock();
#endif
} }
void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
@ -1524,7 +1557,7 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f
} else { } else {
r_owner_deleted = false; r_owner_deleted = false;
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
if (p_is_finalizer) { if (p_is_finalizer && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
// If the native instance is still alive, then it was // If the native instance is still alive, then it was
// referenced from another thread before the finalizer could // referenced from another thread before the finalizer could
// unreference it and delete it, so we want to keep it. // unreference it and delete it, so we want to keep it.
@ -1651,6 +1684,8 @@ void CSharpInstance::notification(int p_notification) {
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
// to be sent at least once, which happens right before the call to the destructor. // to be sent at least once, which happens right before the call to the destructor.
predelete_notified = true;
if (base_ref) { if (base_ref) {
// It's not safe to proceed if the owner derives Reference and the refcount reached 0. // It's not safe to proceed if the owner derives Reference and the refcount reached 0.
// At this point, Dispose() was already called (manually or from the finalizer) so // At this point, Dispose() was already called (manually or from the finalizer) so
@ -1666,10 +1701,8 @@ void CSharpInstance::notification(int p_notification) {
MonoObject *mono_object = get_mono_object(); MonoObject *mono_object = get_mono_object();
ERR_FAIL_NULL(mono_object); ERR_FAIL_NULL(mono_object);
GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
MonoException *exc = NULL; MonoException *exc = NULL;
thunk(mono_object, (MonoObject **)&exc); GDMonoUtils::dispose(mono_object, &exc);
if (exc) { if (exc) {
GDMonoUtils::set_pending_exception(exc); GDMonoUtils::set_pending_exception(exc);
@ -1720,12 +1753,32 @@ CSharpInstance::CSharpInstance() :
owner(NULL), owner(NULL),
base_ref(false), base_ref(false),
ref_dying(false), ref_dying(false),
unsafe_referenced(false) { unsafe_referenced(false),
predelete_notified(false) {
} }
CSharpInstance::~CSharpInstance() { CSharpInstance::~CSharpInstance() {
if (gchandle.is_valid()) { if (gchandle.is_valid()) {
if (!predelete_notified && !ref_dying) {
// This destructor is not called from the owners destructor.
// This could be being called from the owner's set_script_instance method,
// meaning this script is being replaced with another one. If this is the case,
// we must call Dispose here, because Dispose calls owner->set_script_instance(NULL)
// and that would mess up with the new script instance if called later.
MonoObject *mono_object = gchandle->get_target();
if (mono_object) {
MonoException *exc = NULL;
GDMonoUtils::dispose(mono_object, &exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
}
}
}
gchandle->release(); // Make sure it's released gchandle->release(); // Make sure it's released
} }
@ -1734,9 +1787,7 @@ CSharpInstance::~CSharpInstance() {
} }
if (script.is_valid() && owner) { if (script.is_valid() && owner) {
#ifndef NO_THREADS SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::singleton->lock->lock();
#endif
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
// CSharpInstance must not be created unless it's going to be added to the list for sure // CSharpInstance must not be created unless it's going to be added to the list for sure
@ -1746,10 +1797,6 @@ CSharpInstance::~CSharpInstance() {
#else #else
script->instances.erase(owner); script->instances.erase(owner);
#endif #endif
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->unlock();
#endif
} }
} }
@ -1882,10 +1929,8 @@ bool CSharpScript::_update_exports() {
// Dispose the temporary managed instance // Dispose the temporary managed instance
GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
MonoException *exc = NULL; MonoException *exc = NULL;
thunk(tmp_object, (MonoObject **)&exc); GDMonoUtils::dispose(tmp_object, &exc);
if (exc) { if (exc) {
ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
@ -2312,17 +2357,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
ERR_FAIL_V(NULL); ERR_FAIL_V(NULL);
} }
uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(mono_object); // we might lock after this, so pin it // Tie managed to unmanaged
instance->gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS {
CSharpLanguage::singleton->lock->lock(); SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
#endif instances.insert(instance->owner);
}
instances.insert(instance->owner);
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->unlock();
#endif
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner);
@ -2330,13 +2371,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
ctor->invoke(mono_object, p_args); ctor->invoke(mono_object, p_args);
// Tie managed to unmanaged
instance->gchandle = MonoGCHandle::create_strong(mono_object);
/* STEP 3, PARTY */ /* STEP 3, PARTY */
MonoGCHandle::free_handle(pinned_gchandle);
//@TODO make thread safe //@TODO make thread safe
return instance; return instance;
} }
@ -2411,17 +2447,8 @@ PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_t
bool CSharpScript::instance_has(const Object *p_this) const { bool CSharpScript::instance_has(const Object *p_this) const {
#ifndef NO_THREADS SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::singleton->lock->lock(); return instances.has((Object *)p_this);
#endif
bool ret = instances.has((Object *)p_this);
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->unlock();
#endif
return ret;
} }
bool CSharpScript::has_source_code() const { bool CSharpScript::has_source_code() const {
@ -2454,15 +2481,11 @@ bool CSharpScript::has_method(const StringName &p_method) const {
Error CSharpScript::reload(bool p_keep_state) { Error CSharpScript::reload(bool p_keep_state) {
#ifndef NO_THREADS bool has_instances;
CSharpLanguage::singleton->lock->lock(); {
#endif SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
has_instances = instances.size();
bool has_instances = instances.size(); }
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->unlock();
#endif
ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE);
@ -2648,35 +2671,19 @@ CSharpScript::CSharpScript() :
_resource_path_changed(); _resource_path_changed();
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
{
#ifndef NO_THREADS SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::get_singleton()->lock->lock(); CSharpLanguage::get_singleton()->script_list.add(&this->script_list);
}
#endif #endif
CSharpLanguage::get_singleton()->script_list.add(&script_list);
#ifndef NO_THREADS
CSharpLanguage::get_singleton()->lock->unlock();
#endif
#endif // DEBUG_ENABLED
} }
CSharpScript::~CSharpScript() { CSharpScript::~CSharpScript() {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
#ifndef NO_THREADS CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
CSharpLanguage::get_singleton()->lock->lock();
#endif #endif
CSharpLanguage::get_singleton()->script_list.remove(&script_list);
#ifndef NO_THREADS
CSharpLanguage::get_singleton()->lock->unlock();
#endif
#endif // DEBUG_ENABLED
} }
/*************** RESOURCE ***************/ /*************** RESOURCE ***************/

View file

@ -82,6 +82,21 @@ class CSharpScript : public Script {
Set<Object *> instances; Set<Object *> instances;
#ifdef DEBUG_ENABLED
Set<ObjectID> pending_reload_instances;
#endif
struct StateBackup {
// TODO
// Replace with buffer containing the serialized state of managed scripts.
// Keep variant state backup to use only with script instance placeholders.
List<Pair<StringName, Variant> > properties;
};
#ifdef TOOLS_ENABLED
Map<ObjectID, CSharpScript::StateBackup> pending_reload_state;
#endif
String source; String source;
StringName name; StringName name;
@ -105,10 +120,6 @@ class CSharpScript : public Script {
virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder);
#endif #endif
#ifdef DEBUG_ENABLED
Map<ObjectID, List<Pair<StringName, Variant> > > pending_reload_state;
#endif
Map<StringName, PropertyInfo> member_info; Map<StringName, PropertyInfo> member_info;
void _clear(); void _clear();
@ -184,6 +195,7 @@ class CSharpInstance : public ScriptInstance {
bool base_ref; bool base_ref;
bool ref_dying; bool ref_dying;
bool unsafe_referenced; bool unsafe_referenced;
bool predelete_notified;
Ref<CSharpScript> script; Ref<CSharpScript> script;
Ref<MonoGCHandle> gchandle; Ref<MonoGCHandle> gchandle;
@ -253,11 +265,9 @@ class CSharpLanguage : public ScriptLanguage {
GDMono *gdmono; GDMono *gdmono;
SelfList<CSharpScript>::List script_list; SelfList<CSharpScript>::List script_list;
Mutex *lock; Mutex *script_instances_mutex;
Mutex *script_bind_lock; Mutex *script_gchandle_release_mutex;
Mutex *script_gchandle_release_lock; Mutex *language_bind_mutex;
Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
Map<Object *, CSharpScriptBinding> script_bindings; Map<Object *, CSharpScriptBinding> script_bindings;
@ -294,7 +304,8 @@ public:
bool debug_break_parse(const String &p_file, int p_line, const String &p_error); bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
void reload_assemblies_if_needed(bool p_soft_reload); bool is_assembly_reloading_needed();
void reload_assemblies(bool p_soft_reload);
#endif #endif
void project_assembly_loaded(); void project_assembly_loaded();

View file

@ -475,7 +475,9 @@ MonoReloadNode *MonoReloadNode::singleton = NULL;
void MonoReloadNode::_reload_timer_timeout() { void MonoReloadNode::_reload_timer_timeout() {
CSharpLanguage::get_singleton()->reload_assemblies_if_needed(false); if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
} }
void MonoReloadNode::restart_reload_timer() { void MonoReloadNode::restart_reload_timer() {
@ -493,7 +495,9 @@ void MonoReloadNode::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case MainLoop::NOTIFICATION_WM_FOCUS_IN: { case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
restart_reload_timer(); restart_reload_timer();
CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
} break; } break;
default: { default: {
} break; } break;

View file

@ -154,10 +154,14 @@ void MonoBottomPanel::_build_project_pressed() {
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
ERR_FAIL_COND(metadata_err != OK); ERR_FAIL_COND(metadata_err != OK);
GodotSharpBuilds::get_singleton()->build_project_blocking("Tools"); bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
MonoReloadNode::get_singleton()->restart_reload_timer(); if (build_success) {
CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); MonoReloadNode::get_singleton()->restart_reload_timer();
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
}
} }
void MonoBottomPanel::_view_log_pressed() { void MonoBottomPanel::_view_log_pressed() {

View file

@ -421,7 +421,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_dict) { if (is_dict) {
@ -433,7 +433,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
exc = NULL; exc = NULL;
GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_array) { if (is_array) {

View file

@ -163,7 +163,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_dict) { if (is_dict) {
@ -172,7 +172,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
exc = NULL; exc = NULL;
GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_array) { if (is_array) {
@ -192,8 +192,11 @@ String mono_to_utf8_string(MonoString *p_mono_string) {
MonoError error; MonoError error;
char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error);
ERR_EXPLAIN("Conversion of MonoString to UTF8 failed."); if (!mono_error_ok(&error)) {
ERR_FAIL_COND_V(!mono_error_ok(&error), String()); ERR_PRINTS(String("Failed to convert MonoString* to UTF-8: ") + mono_error_get_message(&error));
mono_error_cleanup(&error);
return String();
}
String ret = String::utf8(utf8); String ret = String::utf8(utf8);
@ -546,7 +549,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_dict) { if (is_dict) {
@ -555,7 +558,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
exc = NULL; exc = NULL;
GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_array) { if (is_array) {
@ -710,16 +713,14 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (CACHED_CLASS(Array) == type_class) { if (CACHED_CLASS(Array) == type_class) {
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::Array_GetPtr get_ptr = CACHED_METHOD_THUNK(Array, GetPtr); Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, (MonoObject **)&exc);
Array *ptr = get_ptr(p_obj, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant(); return ptr ? Variant(*ptr) : Variant();
} }
if (CACHED_CLASS(Dictionary) == type_class) { if (CACHED_CLASS(Dictionary) == type_class) {
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::Dictionary_GetPtr get_ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr); Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, (MonoObject **)&exc);
Dictionary *ptr = get_ptr(p_obj, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant(); return ptr ? Variant(*ptr) : Variant();
} }
@ -731,7 +732,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
MonoException *exc = NULL; MonoException *exc = NULL;
GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType); GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_dict) { if (is_dict) {
@ -744,7 +745,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
exc = NULL; exc = NULL;
GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType); GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc); MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc); UNLIKELY_UNHANDLED_EXCEPTION(exc);
if (is_array) { if (is_array) {

View file

@ -694,4 +694,8 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
} }
} }
void dispose(MonoObject *p_mono_object, MonoException **r_exc) {
invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, (MonoObject **)r_exc);
}
} // namespace GDMonoUtils } // namespace GDMonoUtils

View file

@ -243,6 +243,8 @@ MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_param
uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error); uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error);
void dispose(MonoObject *p_mono_object, MonoException **r_exc);
} // namespace GDMonoUtils } // namespace GDMonoUtils
#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL)))
@ -267,4 +269,93 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
#define GD_MONO_END_RUNTIME_INVOKE \ #define GD_MONO_END_RUNTIME_INVOKE \
_runtime_invoke_count_ref -= 1; _runtime_invoke_count_ref -= 1;
inline void invoke_method_thunk(void (*p_method_thunk)()) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk();
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R>
R invoke_method_thunk(R (*p_method_thunk)()) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk();
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
template <class P1>
void invoke_method_thunk(void (*p_method_thunk)(P1), P1 p_arg1) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk(p_arg1);
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R, class P1>
R invoke_method_thunk(R (*p_method_thunk)(P1), P1 p_arg1) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk(p_arg1);
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
template <class P1, class P2>
void invoke_method_thunk(void (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk(p_arg1, p_arg2);
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R, class P1, class P2>
R invoke_method_thunk(R (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk(p_arg1, p_arg2);
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
template <class P1, class P2, class P3>
void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk(p_arg1, p_arg2, p_arg3);
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R, class P1, class P2, class P3>
R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk(p_arg1, p_arg2, p_arg3);
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
template <class P1, class P2, class P3, class P4>
void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R, class P1, class P2, class P3, class P4>
R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
template <class P1, class P2, class P3, class P4, class P5>
void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
GD_MONO_END_RUNTIME_INVOKE;
}
template <class R, class P1, class P2, class P3, class P4, class P5>
R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
GD_MONO_END_RUNTIME_INVOKE;
return r;
}
#endif // GD_MONOUTILS_H #endif // GD_MONOUTILS_H

View file

@ -98,11 +98,9 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc
mono_array_set(signal_args, MonoObject *, i, boxed); mono_array_set(signal_args, MonoObject *, i, boxed);
} }
GDMonoUtils::SignalAwaiter_SignalCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback);
MonoException *exc = NULL; MonoException *exc = NULL;
GD_MONO_BEGIN_RUNTIME_INVOKE; GD_MONO_BEGIN_RUNTIME_INVOKE;
thunk(get_target(), signal_args, (MonoObject **)&exc); invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback), get_target(), signal_args, (MonoObject **)&exc);
GD_MONO_END_RUNTIME_INVOKE; GD_MONO_END_RUNTIME_INVOKE;
if (exc) { if (exc) {
@ -129,14 +127,12 @@ SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
SignalAwaiterHandle::~SignalAwaiterHandle() { SignalAwaiterHandle::~SignalAwaiterHandle() {
if (!completed) { if (!completed) {
GDMonoUtils::SignalAwaiter_FailureCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback);
MonoObject *awaiter = get_target(); MonoObject *awaiter = get_target();
if (awaiter) { if (awaiter) {
MonoException *exc = NULL; MonoException *exc = NULL;
GD_MONO_BEGIN_RUNTIME_INVOKE; GD_MONO_BEGIN_RUNTIME_INVOKE;
thunk(awaiter, (MonoObject **)&exc); invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback), awaiter, (MonoObject **)&exc);
GD_MONO_END_RUNTIME_INVOKE; GD_MONO_END_RUNTIME_INVOKE;
if (exc) { if (exc) {

View file

@ -31,15 +31,17 @@
#ifndef UTIL_MACROS_H #ifndef UTIL_MACROS_H
#define UTIL_MACROS_H #define UTIL_MACROS_H
#define _GD_VARNAME_CONCAT_B(m_ignore, m_name) m_name
#define _GD_VARNAME_CONCAT_A(m_a, m_b, m_c) _GD_VARNAME_CONCAT_B(hello there, m_a##m_b##m_c)
#define _GD_VARNAME_CONCAT(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A(m_a, m_b, m_c)
#define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT(m_name, _, __COUNTER__)
// noreturn // noreturn
#if __cpp_static_assert #if __cpp_static_assert
#define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed") #define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed")
#else #else
#define _GD_STATIC_ASSERT_VARNAME_CONCAT_B(m_ignore, m_name) m_name #define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)]
#define _GD_STATIC_ASSERT_VARNAME_CONCAT_A(m_a, m_b) GD_STATIC_ASSERT_VARNAME_CONCAT_B(hello there, m_a##m_b)
#define _GD_STATIC_ASSERT_VARNAME_CONCAT(m_a, m_b) GD_STATIC_ASSERT_VARNAME_CONCAT_A(m_a, m_b)
#define GD_STATIC_ASSERT(m_cond) typedef int GD_STATIC_ASSERT_VARNAME_CONCAT(godot_static_assert_, __COUNTER__)[((m_cond) ? 1 : -1)]
#endif #endif
#undef _NO_RETURN_ #undef _NO_RETURN_

View file

@ -0,0 +1,67 @@
/*************************************************************************/
/* mutex_utils.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MUTEX_UTILS_H
#define MUTEX_UTILS_H
#include "core/error_macros.h"
#include "core/os/mutex.h"
#include "macros.h"
class ScopedMutexLock {
Mutex *mutex;
public:
ScopedMutexLock(Mutex *mutex) {
this->mutex = mutex;
#ifndef NO_THREADS
#ifdef DEBUG_ENABLED
CRASH_COND(!mutex);
#endif
this->mutex->lock();
#endif
}
~ScopedMutexLock() {
#ifndef NO_THREADS
#ifdef DEBUG_ENABLED
CRASH_COND(!mutex);
#endif
mutex->unlock();
#endif
}
};
#define SCOPED_MUTEX_LOCK(m_mutex) ScopedMutexLock GD_UNIQUE_NAME(__scoped_mutex_lock__)(m_mutex);
// TODO: Add version that receives a lambda instead, once C++11 is allowed
#endif // MUTEX_UTILS_H