diff options
Diffstat (limited to 'modules/mono/mono_gd/gd_mono.cpp')
-rw-r--r-- | modules/mono/mono_gd/gd_mono.cpp | 771 |
1 files changed, 771 insertions, 0 deletions
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp new file mode 100644 index 0000000000..77f01842bb --- /dev/null +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -0,0 +1,771 @@ +/*************************************************************************/ +/* gd_mono.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#include "gd_mono.h" + +#include <mono/metadata/mono-config.h> +#include <mono/metadata/mono-debug.h> + +#include "os/dir_access.h" +#include "os/file_access.h" +#include "os/os.h" +#include "os/thread.h" +#include "project_settings.h" + +#include "../csharp_script.h" +#include "../utils/path_utils.h" +#include "gd_mono_utils.h" + +#ifdef TOOLS_ENABLED +#include "../editor/godotsharp_editor.h" +#endif + +#ifdef MONO_PRINT_HANDLER_ENABLED +void gdmono_MonoPrintCallback(const char *string, mono_bool is_stdout) { + + if (is_stdout) { + OS::get_singleton()->print(string); + } else { + OS::get_singleton()->printerr(string); + } +} +#endif + +GDMono *GDMono::singleton = NULL; + +#ifdef DEBUG_ENABLED +static bool _wait_for_debugger_msecs(uint32_t p_msecs) { + + do { + if (mono_is_debugger_attached()) + return true; + + int last_tick = OS::get_singleton()->get_ticks_msec(); + + OS::get_singleton()->delay_usec((p_msecs < 25 ? p_msecs : 25) * 1000); + + int tdiff = OS::get_singleton()->get_ticks_msec() - last_tick; + + if (tdiff > p_msecs) { + p_msecs = 0; + } else { + p_msecs -= tdiff; + } + } while (p_msecs > 0); + + return mono_is_debugger_attached(); +} +#endif + +#ifdef TOOLS_ENABLED +// temporary workaround. should be provided from Main::setup/setup2 instead +bool _is_project_manager_requested() { + + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { + const String &arg = E->get(); + if (arg == "-p" || arg == "--project-manager") + return true; + } + + return false; +} +#endif + +#ifdef DEBUG_ENABLED +void gdmono_debug_init() { + + mono_debug_init(MONO_DEBUG_FORMAT_MONO); + + int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685); + bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); + int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() || + ProjectSettings::get_singleton()->get_resource_path().empty() || + _is_project_manager_requested()) { + return; + } +#endif + + CharString da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + + ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) + .utf8(); + // --debugger-agent=help + const char *options[] = { + "--soft-breakpoints", + da_args.get_data() + }; + mono_jit_parse_options(2, (char **)options); +} +#endif + +void GDMono::initialize() { + + ERR_FAIL_NULL(Engine::get_singleton()); + + OS::get_singleton()->print("Initializing mono...\n"); + +#ifdef DEBUG_METHODS_ENABLED + _initialize_and_check_api_hashes(); +#endif + + GDMonoLog::get_singleton()->initialize(); + +#ifdef MONO_PRINT_HANDLER_ENABLED + mono_trace_set_print_handler(gdmono_MonoPrintCallback); + mono_trace_set_printerr_handler(gdmono_MonoPrintCallback); +#endif + +#ifdef WINDOWS_ENABLED + mono_reg_info = MonoRegUtils::find_mono(); + + CharString assembly_dir; + CharString config_dir; + + if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { + assembly_dir = mono_reg_info.assembly_dir.utf8(); + } + + if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { + config_dir = mono_reg_info.config_dir.utf8(); + } + + mono_set_dirs(assembly_dir.length() ? assembly_dir.get_data() : NULL, + config_dir.length() ? config_dir.get_data() : NULL); +#else + mono_set_dirs(NULL, NULL); +#endif + + GDMonoAssembly::initialize(); + +#ifdef DEBUG_ENABLED + gdmono_debug_init(); +#endif + + mono_config_parse(NULL); + + root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + + ERR_EXPLAIN("Mono: Failed to initialize runtime"); + ERR_FAIL_NULL(root_domain); + + GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); + + runtime_initialized = true; + + OS::get_singleton()->print("Mono: Runtime initialized\n"); + + // mscorlib assembly MUST be present at initialization + ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); + ERR_FAIL_COND(!_load_corlib_assembly()); + +#ifdef TOOLS_ENABLED + // The tools domain must be loaded here, before the scripts domain. + // Otherwise domain unload on the scripts domain will hang indefinitely. + + ERR_EXPLAIN("Mono: Failed to load tools domain"); + ERR_FAIL_COND(_load_tools_domain() != OK); + + // TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation) + ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly"); + ERR_FAIL_COND(!_load_editor_tools_assembly()); +#endif + + ERR_EXPLAIN("Mono: Failed to load scripts domain"); + ERR_FAIL_COND(_load_scripts_domain() != OK); + +#ifdef DEBUG_ENABLED + bool debugger_attached = _wait_for_debugger_msecs(500); + if (!debugger_attached && OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Debugger wait timeout\n"); +#endif + + _register_internal_calls(); + + // The following assemblies are not required at initialization + _load_all_script_assemblies(); + + OS::get_singleton()->print("Mono: EVERYTHING OK\n"); +} + +#ifndef MONO_GLUE_DISABLED +namespace GodotSharpBindings { + +uint64_t get_core_api_hash(); +uint64_t get_editor_api_hash(); + +void register_generated_icalls(); +} // namespace GodotSharpBindings +#endif + +void GDMono::_register_internal_calls() { +#ifndef MONO_GLUE_DISABLED + GodotSharpBindings::register_generated_icalls(); +#endif + +#ifdef TOOLS_ENABLED + GodotSharpBuilds::_register_internal_calls(); +#endif +} + +#ifdef DEBUG_METHODS_ENABLED +void GDMono::_initialize_and_check_api_hashes() { + + api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE); + +#ifndef MONO_GLUE_DISABLED + if (api_core_hash != GodotSharpBindings::get_core_api_hash()) { + ERR_PRINT("Mono: Core API hash mismatch!"); + } +#endif + +#ifdef TOOLS_ENABLED + api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR); + +#ifndef MONO_GLUE_DISABLED + if (api_editor_hash != GodotSharpBindings::get_editor_api_hash()) { + ERR_PRINT("Mono: Editor API hash mismatch!"); + } +#endif + +#endif // TOOLS_ENABLED +} +#endif // DEBUG_METHODS_ENABLED + +void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { + + assemblies[p_domain_id][p_assembly->get_name()] = p_assembly; +} + +bool GDMono::_load_assembly(const String &p_name, GDMonoAssembly **r_assembly) { + + CRASH_COND(!r_assembly); + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print((String() + "Mono: Loading assembly " + p_name + "...\n").utf8()); + + MonoImageOpenStatus status; + MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); + MonoAssembly *assembly = mono_assembly_load_full(aname, NULL, &status, false); + mono_assembly_name_free(aname); + + if (!assembly) + return false; + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + + GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); + + if (stored_assembly) { + // Loaded by our preload hook (status is not initialized when returning from a preload hook) + ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false); + *r_assembly = *stored_assembly; + } else { + ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false); + + MonoImage *assembly_image = mono_assembly_get_image(assembly); + ERR_FAIL_NULL_V(assembly_image, false); + + const char *path = mono_image_get_filename(assembly_image); + + *r_assembly = memnew(GDMonoAssembly(p_name, path)); + Error error = (*r_assembly)->wrapper_for_image(assembly_image); + + if (error != OK) { + memdelete(*r_assembly); + *r_assembly = NULL; + ERR_FAIL_V(false); + } + } + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print(String("Mono: Assembly " + p_name + " loaded from path: " + (*r_assembly)->get_path() + "\n").utf8()); + + return true; +} + +bool GDMono::_load_corlib_assembly() { + + if (corlib_assembly) + return true; + + bool success = _load_assembly("mscorlib", &corlib_assembly); + + if (success) + GDMonoUtils::update_corlib_cache(); + + return success; +} + +bool GDMono::_load_core_api_assembly() { + + if (api_assembly) + return true; + + bool success = _load_assembly(API_ASSEMBLY_NAME, &api_assembly); + + if (success) + GDMonoUtils::update_godot_api_cache(); + + return success; +} + +#ifdef TOOLS_ENABLED +bool GDMono::_load_editor_api_assembly() { + + if (editor_api_assembly) + return true; + + return _load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); +} +#endif + +#ifdef TOOLS_ENABLED +bool GDMono::_load_editor_tools_assembly() { + + if (editor_tools_assembly) + return true; + + _GDMONO_SCOPE_DOMAIN_(tools_domain) + + return _load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly); +} +#endif + +bool GDMono::_load_project_assembly() { + + if (project_assembly) + return true; + + String project_assembly_name = ProjectSettings::get_singleton()->get("application/config/name"); + + bool success = _load_assembly(project_assembly_name, &project_assembly); + + if (success) + mono_assembly_set_main(project_assembly->get_assembly()); + + return success; +} + +bool GDMono::_load_all_script_assemblies() { + +#ifndef MONO_GLUE_DISABLED + if (!_load_core_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n"); + return false; + } else { +#ifdef TOOLS_ENABLED + if (!_load_editor_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load Editor API assembly\n"); + return false; + } +#endif + } + + if (!_load_project_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load project assembly\n"); + return false; + } + + return true; +#else + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Mono: Glue disbled, ignoring script assemblies\n"); + + return true; +#endif +} + +Error GDMono::_load_scripts_domain() { + + ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Loading scripts domain...\n"); + } + + scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); + + ERR_EXPLAIN("Mono: Could not create scripts app domain"); + ERR_FAIL_NULL_V(scripts_domain, ERR_CANT_CREATE); + + mono_domain_set(scripts_domain, true); + + return OK; +} + +Error GDMono::_unload_scripts_domain() { + + ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Unloading scripts domain...\n"); + } + + _GodotSharp::get_singleton()->_dispose_callback(); + + if (mono_domain_get() != root_domain) + mono_domain_set(root_domain, true); + + finalizing_scripts_domain = true; + mono_domain_finalize(scripts_domain, 2000); + finalizing_scripts_domain = false; + + _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); + + api_assembly = NULL; + project_assembly = NULL; +#ifdef TOOLS_ENABLED + editor_api_assembly = NULL; +#endif + + MonoDomain *domain = scripts_domain; + scripts_domain = NULL; + + _GodotSharp::get_singleton()->_dispose_callback(); + + MonoObject *ex = NULL; + mono_domain_try_unload(domain, &ex); + + if (ex) { + ERR_PRINT("Exception thrown when unloading scripts domain:"); + mono_print_unhandled_exception(ex); + return FAILED; + } + + return OK; +} + +#ifdef TOOLS_ENABLED +Error GDMono::_load_tools_domain() { + + ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Loading tools domain...\n"); + } + + tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain"); + + ERR_EXPLAIN("Mono: Could not create tools app domain"); + ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE); + + return OK; +} +#endif + +#ifdef TOOLS_ENABLED +Error GDMono::reload_scripts_domain() { + + ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); + + if (scripts_domain) { + Error err = _unload_scripts_domain(); + if (err != OK) { + ERR_PRINT("Mono: Failed to unload scripts domain"); + return err; + } + } + + Error err = _load_scripts_domain(); + if (err != OK) { + ERR_PRINT("Mono: Failed to load scripts domain"); + return err; + } + + if (!_load_all_script_assemblies()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load script assemblies\n"); + return ERR_CANT_OPEN; + } + + return OK; +} +#endif + +GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { + + MonoImage *image = mono_class_get_image(p_raw_class); + + if (image == corlib_assembly->get_image()) + return corlib_assembly->get_class(p_raw_class); + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + GDMonoAssembly *assembly = domain_assemblies.get(*k); + if (assembly->get_image() == image) { + GDMonoClass *klass = assembly->get_class(p_raw_class); + + if (klass) + return klass; + } + } + + return NULL; +} + +void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { + + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + memdelete(domain_assemblies.get(*k)); + } + + assemblies.erase(p_domain_id); +} + +GDMono::GDMono() { + + singleton = this; + + gdmono_log = memnew(GDMonoLog); + + runtime_initialized = false; + finalizing_scripts_domain = false; + + root_domain = NULL; + scripts_domain = NULL; +#ifdef TOOLS_ENABLED + tools_domain = NULL; +#endif + + corlib_assembly = NULL; + api_assembly = NULL; + project_assembly = NULL; +#ifdef TOOLS_ENABLED + editor_api_assembly = NULL; + editor_tools_assembly = NULL; +#endif + +#ifdef DEBUG_METHODS_ENABLED + api_core_hash = 0; +#ifdef TOOLS_ENABLED + api_editor_hash = 0; +#endif +#endif +} + +GDMono::~GDMono() { + + if (runtime_initialized) { + + if (scripts_domain) { + + Error err = _unload_scripts_domain(); + if (err != OK) { + WARN_PRINT("Mono: Failed to unload scripts domain"); + } + } + + const uint32_t *k = NULL; + while ((k = assemblies.next(k))) { + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies.get(*k); + + const String *kk = NULL; + while ((kk = domain_assemblies.next(kk))) { + memdelete(domain_assemblies.get(*kk)); + } + } + assemblies.clear(); + + GDMonoUtils::clear_cache(); + + OS::get_singleton()->print("Mono: Runtime cleanup...\n"); + + runtime_initialized = false; + mono_jit_cleanup(root_domain); + } + + if (gdmono_log) + memdelete(gdmono_log); +} + +_GodotSharp *_GodotSharp::singleton = NULL; + +void _GodotSharp::_dispose_object(Object *p_object) { + + if (p_object->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); + if (cs_instance) { + cs_instance->mono_object_disposed(); + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + if (Object::cast_to<Reference>(p_object)->unreference()) { + memdelete(p_object); + } +} + +void _GodotSharp::_dispose_callback() { + +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + for (List<Object *>::Element *E = obj_delete_queue.front(); E; E = E->next()) { + _dispose_object(E->get()); + } + + for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) { + memdelete(E->get()); + } + + for (List<RID *>::Element *E = rid_delete_queue.front(); E; E = E->next()) { + memdelete(E->get()); + } + + obj_delete_queue.clear(); + np_delete_queue.clear(); + rid_delete_queue.clear(); + queue_empty = true; + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif +} + +void _GodotSharp::attach_thread() { + + GDMonoUtils::attach_current_thread(); +} + +void _GodotSharp::detach_thread() { + + GDMonoUtils::detach_current_thread(); +} + +bool _GodotSharp::is_finalizing_domain() { + + return GDMono::get_singleton()->is_finalizing_scripts_domain(); +} + +bool _GodotSharp::is_domain_loaded() { + + return GDMono::get_singleton()->get_scripts_domain() != NULL; +} + +#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \ + m_queue.push_back(m_inst); \ + if (queue_empty) { \ + queue_empty = false; \ + call_deferred("_dispose_callback"); \ + } + +void _GodotSharp::queue_dispose(Object *p_object) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + _dispose_object(p_object); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::queue_dispose(NodePath *p_node_path) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + memdelete(p_node_path); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(np_delete_queue, p_node_path); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::queue_dispose(RID *p_rid) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + memdelete(p_rid); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(rid_delete_queue, p_rid); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::_bind_methods() { + + ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); + ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread); + + ClassDB::bind_method(D_METHOD("is_finalizing_domain"), &_GodotSharp::is_finalizing_domain); + ClassDB::bind_method(D_METHOD("is_domain_loaded"), &_GodotSharp::is_domain_loaded); + + ClassDB::bind_method(D_METHOD("_dispose_callback"), &_GodotSharp::_dispose_callback); +} + +_GodotSharp::_GodotSharp() { + + singleton = this; + queue_empty = true; +#ifndef NO_THREADS + queue_mutex = Mutex::create(); +#endif +} + +_GodotSharp::~_GodotSharp() { + + singleton = NULL; + + if (queue_mutex) { + memdelete(queue_mutex); + } +} |