diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 1057 |
1 files changed, 609 insertions, 448 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 6809cbdff9..ae25bd3544 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -33,6 +33,8 @@ #include <mono/metadata/threads.h> #include <stdint.h> +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/io/json.h" #include "core/os/file_access.h" #include "core/os/mutex.h" @@ -60,20 +62,18 @@ #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/string_utils.h" -#include "utils/thread_local.h" #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) #ifdef TOOLS_ENABLED static bool _create_project_solution_if_needed() { - String sln_path = GodotSharpDirs::get_project_sln_path(); String csproj_path = GodotSharpDirs::get_project_csproj_path(); if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { // A solution does not yet exist, create a new one - CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL); + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); } @@ -81,31 +81,26 @@ static bool _create_project_solution_if_needed() { } #endif -CSharpLanguage *CSharpLanguage::singleton = NULL; +CSharpLanguage *CSharpLanguage::singleton = nullptr; String CSharpLanguage::get_name() const { - return "C#"; } String CSharpLanguage::get_type() const { - return "CSharpScript"; } String CSharpLanguage::get_extension() const { - return "cs"; } Error CSharpLanguage::execute_file(const String &p_path) { - // ?? return OK; } void CSharpLanguage::init() { - #ifdef DEBUG_METHODS_ENABLED if (OS::get_singleton()->get_cmdline_args().find("--class-db-json")) { class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE); @@ -139,6 +134,8 @@ void CSharpLanguage::init() { } void CSharpLanguage::finish() { + if (finalized) + return; finalizing = true; @@ -146,15 +143,15 @@ void CSharpLanguage::finish() { for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); - if (script_binding.gchandle.is_valid()) { - script_binding.gchandle->release(); + if (!script_binding.gchandle.is_released()) { + script_binding.gchandle.release(); script_binding.inited = false; } } if (gdmono) { memdelete(gdmono); - gdmono = NULL; + gdmono = nullptr; } // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements. @@ -173,11 +170,13 @@ void CSharpLanguage::finish() { } #endif + memdelete(managed_callable_middleman); + finalizing = false; + finalized = true; } void CSharpLanguage::get_reserved_words(List<String> *p_words) const { - static const char *_reserved_words[] = { // Reserved keywords "abstract", @@ -288,7 +287,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { "when", "where", "yield", - 0 + nullptr }; const char **w = _reserved_words; @@ -300,20 +299,18 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { } void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { - p_delimiters->push_back("//"); // single-line comment p_delimiters->push_back("/* */"); // delimited comment } void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { - p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal - p_delimiters->push_back("@\" \""); // verbatim string literal + // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them. + // Generic string highlighting suffices as a workaround for now. } static String get_base_class_name(const String &p_base_class_name, const String p_class_name) { - String base_class = p_base_class_name; if (p_class_name == base_class) { base_class = "Godot." + base_class; @@ -322,7 +319,6 @@ static String get_base_class_name(const String &p_base_class_name, const String } Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { - String script_template = "using " BINDINGS_NAMESPACE ";\n" "using System;\n" "\n" @@ -358,12 +354,10 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin } bool CSharpLanguage::is_using_templates() { - return true; } void CSharpLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) { - String src = p_script->get_source_code(); String base_class_name = get_base_class_name(p_base_class_name, p_class_name); src = src.replace("%BASE%", base_class_name) @@ -373,7 +367,6 @@ void CSharpLanguage::make_template(const String &p_class_name, const String &p_b } String CSharpLanguage::validate_path(const String &p_path) const { - String class_name = p_path.get_file().get_basename(); List<String> keywords; get_reserved_words(&keywords); @@ -384,23 +377,19 @@ String CSharpLanguage::validate_path(const String &p_path) const { } Script *CSharpLanguage::create_script() const { - return memnew(CSharpScript); } bool CSharpLanguage::has_named_classes() const { - return false; } bool CSharpLanguage::supports_builtin_mode() const { - return false; } #ifdef TOOLS_ENABLED static String variant_type_to_managed_name(const String &p_var_type_name) { - if (p_var_type_name.empty()) return "object"; @@ -432,13 +421,12 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { return "byte[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) return "int[]"; - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { -#ifdef REAL_T_IS_DOUBLE - return "double[]"; -#else + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) + return "long[]"; + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) return "float[]"; -#endif - } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) + return "double[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) return "string[]"; if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) @@ -448,12 +436,18 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { if (p_var_type_name == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) return "Color[]"; + if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) + return "SignalInfo"; + Variant::Type var_types[] = { Variant::BOOL, Variant::INT, Variant::VECTOR2, + Variant::VECTOR2I, Variant::RECT2, + Variant::RECT2I, Variant::VECTOR3, + Variant::VECTOR3I, Variant::TRANSFORM2D, Variant::PLANE, Variant::QUAT, @@ -461,8 +455,10 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { Variant::BASIS, Variant::TRANSFORM, Variant::COLOR, + Variant::STRING_NAME, Variant::NODE_PATH, - Variant::_RID + Variant::_RID, + Variant::CALLABLE }; for (unsigned int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) { @@ -516,12 +512,10 @@ String CSharpLanguage::_get_indentation() const { } String CSharpLanguage::debug_get_error() const { - return _debug_error; } int CSharpLanguage::debug_get_stack_level_count() const { - if (_debug_parse_err_line >= 0) return 1; @@ -530,7 +524,6 @@ int CSharpLanguage::debug_get_stack_level_count() const { } int CSharpLanguage::debug_get_stack_level_line(int p_level) const { - if (_debug_parse_err_line >= 0) return _debug_parse_err_line; @@ -539,7 +532,6 @@ int CSharpLanguage::debug_get_stack_level_line(int p_level) const { } String CSharpLanguage::debug_get_stack_level_function(int p_level) const { - if (_debug_parse_err_line >= 0) return String(); @@ -548,7 +540,6 @@ String CSharpLanguage::debug_get_stack_level_function(int p_level) const { } String CSharpLanguage::debug_get_stack_level_source(int p_level) const { - if (_debug_parse_err_line >= 0) return _debug_parse_err_file; @@ -557,9 +548,14 @@ String CSharpLanguage::debug_get_stack_level_source(int p_level) const { } Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { - #ifdef DEBUG_ENABLED - _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + // Printing an error here will result in endless recursion, so we must be careful + static thread_local bool _recursion_flag_ = false; + if (_recursion_flag_) + return Vector<StackInfo>(); + _recursion_flag_ = true; + SCOPE_EXIT { _recursion_flag_ = false; }; + GD_MONO_SCOPE_THREAD_ATTACH; if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) @@ -583,11 +579,16 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { + // Printing an error here will result in endless recursion, so we must be careful + static thread_local bool _recursion_flag_ = false; + if (_recursion_flag_) + return Vector<StackInfo>(); + _recursion_flag_ = true; + SCOPE_EXIT { _recursion_flag_ = false; }; - _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); GD_MONO_SCOPE_THREAD_ATTACH; - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); @@ -651,15 +652,14 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { } void CSharpLanguage::frame() { - - if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { - const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; + if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) { + const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; if (task_scheduler_handle.is_valid()) { MonoObject *task_scheduler = task_scheduler_handle->get_target(); if (task_scheduler) { - MonoException *exc = NULL; + MonoException *exc = nullptr; CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc); if (exc) { @@ -671,7 +671,6 @@ void CSharpLanguage::frame() { } struct CSharpScriptDepSort { - // must support sorting so inheritance works properly (parent must be reloaded first) bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const { if (A == B) @@ -691,7 +690,6 @@ struct CSharpScriptDepSort { }; void CSharpLanguage::reload_all_scripts() { - #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { GD_MONO_SCOPE_THREAD_ATTACH; @@ -701,7 +699,6 @@ void CSharpLanguage::reload_all_scripts() { } void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { - (void)p_script; // UNUSED CRASH_COND(!Engine::get_singleton()->is_editor_hint()); @@ -720,7 +717,6 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef GD_MONO_HOT_RELOAD bool CSharpLanguage::is_assembly_reloading_needed() { - if (!gdmono->is_runtime_initialized()) return false; @@ -737,7 +733,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { if (proj_assembly) { String proj_asm_path = proj_assembly->get_path(); - if (!FileAccess::exists(proj_assembly->get_path())) { + if (!FileAccess::exists(proj_asm_path)) { // 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(appname_safe); if (!FileAccess::exists(proj_asm_path)) @@ -755,13 +751,12 @@ bool CSharpLanguage::is_assembly_reloading_needed() { } 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; { MutexLock lock(script_instances_mutex); @@ -772,10 +767,40 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } } - List<Ref<CSharpScript> > to_reload; + scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order + + // Serialize managed callables + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->self(); + + MonoDelegate *delegate = (MonoDelegate *)managed_callable->delegate_handle.get_target(); + + Array serialized_data; + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate, managed_serialized_data, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to serialize delegate\n"); + } + } + } + + List<Ref<CSharpScript>> to_reload; // We need to keep reference instances alive during reloading - List<Ref<Reference> > ref_instances; + List<Ref<Reference>> ref_instances; for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); @@ -787,15 +812,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // 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()) { Ref<CSharpScript> &script = E->get(); to_reload.push_back(script); if (script->get_path().empty()) { - script->tied_class_name_for_reload = script->script_class->get_name(); + script->tied_class_name_for_reload = script->script_class->get_name_for_lookup(); script->tied_class_namespace_for_reload = script->script_class->get_namespace(); } @@ -843,13 +866,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // TODO: Proper state backup (Not only variants, serialize managed state of scripts) csi->get_properties_state_for_reloading(state.properties); + csi->get_event_signals_state_for_reloading(state.event_signals); owners_map[obj->get_instance_id()] = state; } } // After the state of all instances is saved, clear scripts and script instances - for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = scripts.front(); E; E = E->next()) { Ref<CSharpScript> &script = E->get(); while (script->instances.front()) { @@ -864,7 +888,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { if (gdmono->reload_scripts_domain() != OK) { // Failed to reload the scripts domain // Make sure to add the scripts back to their owners before returning - for (List<Ref<CSharpScript> >::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->get(); for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) { @@ -889,8 +913,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif // 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); + 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, nullptr); } scr->pending_reload_state.erase(obj_id); @@ -900,9 +924,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } - List<Ref<CSharpScript> > to_reload_state; + List<Ref<CSharpScript>> to_reload_state; - for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) { Ref<CSharpScript> script = E->get(); if (!script->get_path().empty()) { @@ -924,12 +948,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); // Search in project and tools assemblies first as those are the most likely to have the class - GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL); + GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr); #ifdef TOOLS_ENABLED if (!script_class) { GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly(); - script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL); + script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr); } #endif @@ -955,7 +979,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpScript::initialize_for_managed_type(script, script_class, native); } - String native_name = NATIVE_GDMONOCLASS_NAME(script->native); + StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native); { for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { @@ -1000,7 +1024,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { continue; } #else - CRASH_COND(si != NULL); + CRASH_COND(si != nullptr); #endif // Re-create script instance obj->set_script(script); // will create the script instance as well @@ -1010,7 +1034,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload_state.push_back(script); } - for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) { + for (List<Ref<CSharpScript>>::Element *E = to_reload_state.front(); E; E = E->next()) { Ref<CSharpScript> script = E->get(); for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) { @@ -1028,19 +1052,84 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; - for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) { + for (List<Pair<StringName, Variant>>::Element *G = state_backup.properties.front(); G; G = G->next()) { obj->get_script_instance()->set(G->get().first, G->get().second); } - // Call OnAfterDeserialization CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); - if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) - obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); + + if (csi) { + for (List<Pair<StringName, Array>>::Element *G = state_backup.event_signals.front(); G; G = G->next()) { + const StringName &name = G->get().first; + const Array &serialized_data = G->get().second; + + Map<StringName, CSharpScript::EventSignal>::Element *match = script->event_signals.find(name); + + if (!match) { + // The event or its signal attribute were removed + continue; + } + + const CSharpScript::EventSignal &event_signal = match->value(); + + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + MonoDelegate *delegate = nullptr; + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ERR_CONTINUE(delegate == nullptr); + event_signal.field->set_value(csi->get_mono_object(), (MonoObject *)delegate); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize event signal delegate\n"); + } + } + + // Call OnAfterDeserialization + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) + obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); + } } script->pending_reload_instances.clear(); } + // Deserialize managed callables + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (Map<ManagedCallable *, Array>::Element *elem = ManagedCallable::instances_pending_reload.front(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->key(); + const Array &serialized_data = elem->value(); + + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + MonoDelegate *delegate = nullptr; + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + + if (success) { + ERR_CONTINUE(delegate == nullptr); + managed_callable->set_delegate(delegate); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize delegate\n"); + } + } + + ManagedCallable::instances_pending_reload.clear(); + } + #ifdef TOOLS_ENABLED // 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()) { @@ -1052,7 +1141,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #endif void CSharpLanguage::_load_scripts_metadata() { - scripts_metadata.clear(); String scripts_metadata_filename = "scripts_metadata."; @@ -1097,24 +1185,20 @@ void CSharpLanguage::_load_scripts_metadata() { } void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("cs"); } #ifdef TOOLS_ENABLED Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col); } bool CSharpLanguage::overrides_external_editor() { - return get_godotsharp_editor()->call("OverridesExternalEditor"); } #endif void CSharpLanguage::thread_enter() { - #if 0 if (gdmono->is_runtime_initialized()) { GDMonoUtils::attach_current_thread(); @@ -1123,7 +1207,6 @@ void CSharpLanguage::thread_enter() { } void CSharpLanguage::thread_exit() { - #if 0 if (gdmono->is_runtime_initialized()) { GDMonoUtils::detach_current_thread(); @@ -1132,13 +1215,12 @@ void CSharpLanguage::thread_exit() { } bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { - // Not a parser error in our case, but it's still used for other type of errors - if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false, true); + EngineDebugger::get_script_debugger()->debug(this, false, true); return true; } else { return false; @@ -1146,12 +1228,11 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S } bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { - - if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { _debug_parse_err_line = -1; _debug_parse_err_file = ""; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + EngineDebugger::get_script_debugger()->debug(this, p_allow_continue); return true; } else { return false; @@ -1161,31 +1242,41 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { void CSharpLanguage::_on_scripts_domain_unloaded() { for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); + script_binding.gchandle.release(); script_binding.inited = false; } + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { + ManagedCallable *managed_callable = elem->self(); + managed_callable->delegate_handle.release(); + managed_callable->delegate_invoke = nullptr; + } + } + scripts_metadata_invalidated = true; } #ifdef TOOLS_ENABLED void CSharpLanguage::_editor_init_callback() { - register_editor_internal_calls(); // Initialize GodotSharpEditor GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); - CRASH_COND(editor_klass == NULL); + CRASH_COND(editor_klass == nullptr); MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); - CRASH_COND(mono_object == NULL); + CRASH_COND(mono_object == nullptr); - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); UNHANDLED_EXCEPTION(exc); EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object)); - CRASH_COND(godotsharp_editor == NULL); + CRASH_COND(godotsharp_editor == nullptr); // Enable it as a plugin EditorNode::add_editor_plugin(godotsharp_editor); @@ -1196,71 +1287,53 @@ void CSharpLanguage::_editor_init_callback() { #endif void CSharpLanguage::set_language_index(int p_idx) { - ERR_FAIL_COND(lang_idx != -1); lang_idx = p_idx; } -void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) { - - if (!p_gchandle->is_released()) { // Do not lock unnecessarily +void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) { + if (!p_gchandle.is_released()) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - p_gchandle->release(); + p_gchandle.release(); } } -void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) { - - uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it +void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) { + uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it - if (!p_gchandle->is_released()) { // Do not lock unnecessarily + if (!p_gchandle.is_released()) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - MonoObject *target = p_gchandle->get_target(); + MonoObject *target = p_gchandle.get_target(); // We release the gchandle if it points to the MonoObject* we expect (otherwise it was // 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 // avoid locking other threads unnecessarily). - if (target == p_expected_obj || target == NULL) { - p_gchandle->release(); + if (target == p_expected_obj || target == nullptr) { + p_gchandle.release(); } } - MonoGCHandle::free_handle(pinned_gchandle); + GDMonoUtils::free_gchandle(pinned_gchandle); } CSharpLanguage::CSharpLanguage() { - ERR_FAIL_COND_MSG(singleton, "C# singleton already exist."); singleton = this; - - finalizing = false; - - gdmono = NULL; - - lang_idx = -1; - - scripts_metadata_invalidated = true; - -#ifdef TOOLS_ENABLED - godotsharp_editor = NULL; -#endif } CSharpLanguage::~CSharpLanguage() { - finish(); - singleton = NULL; + singleton = nullptr; } bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object) { - #ifdef DEBUG_ENABLED // I don't trust you if (p_object->get_script_instance()) { CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - CRASH_COND(csharp_instance != NULL && !csharp_instance->is_destructing_script_instance()); + CRASH_COND(csharp_instance != nullptr && !csharp_instance->is_destructing_script_instance()); } #endif @@ -1284,7 +1357,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b r_script_binding.inited = true; r_script_binding.type_name = type_name; r_script_binding.wrapper_class = type_class; // cache - r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object); + r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object); r_script_binding.owner = p_object; // Tie managed to unmanaged @@ -1304,7 +1377,6 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b } void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { - MutexLock lock(language_bind_mutex); Map<Object *, CSharpScriptBinding>::Element *match = script_bindings.find(p_object); @@ -1314,19 +1386,17 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { CSharpScriptBinding script_binding; if (!setup_csharp_script_binding(script_binding, p_object)) - return NULL; + return nullptr; return (void *)insert_script_binding(p_object, script_binding); } Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding) { - return script_bindings.insert(p_object, p_script_binding); } void CSharpLanguage::free_instance_binding_data(void *p_data) { - - if (GDMono::get_singleton() == NULL) { + if (GDMono::get_singleton() == nullptr) { #ifdef DEBUG_ENABLED CRASH_COND(!script_bindings.empty()); #endif @@ -1349,10 +1419,11 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (script_binding.inited) { // Set the native instance field to IntPtr.Zero, if not yet garbage collected. // This is done to avoid trying to dispose the native instance from Dispose(bool). - MonoObject *mono_object = script_binding.gchandle->get_target(); + MonoObject *mono_object = script_binding.gchandle.get_target(); if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr); } + script_binding.gchandle.release(); } script_bindings.erase(data); @@ -1360,7 +1431,6 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { } void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { - Reference *ref_owner = Object::cast_to<Reference>(p_object); #ifdef DEBUG_ENABLED @@ -1372,31 +1442,30 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + MonoGCHandleData &gchandle = script_binding.gchandle; if (!script_binding.inited) return; - if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (ref_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. - MonoObject *target = gchandle->get_target(); + MonoObject *target = gchandle.get_target(); if (!target) return; // Called after the managed side was collected, so nothing to do here // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target); - gchandle->release(); - gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); + MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); + gchandle.release(); + gchandle = strong_gchandle; } } bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { - Reference *ref_owner = Object::cast_to<Reference>(p_object); #ifdef DEBUG_ENABLED @@ -1408,27 +1477,27 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CRASH_COND(!data); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + MonoGCHandleData &gchandle = script_binding.gchandle; int refcount = ref_owner->reference_get_count(); if (!script_binding.inited) return refcount == 0; - if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !gchandle.is_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. - MonoObject *target = gchandle->get_target(); + MonoObject *target = gchandle.get_target(); if (!target) return refcount == 0; // Called after the managed side was collected, so nothing to do here // Release the current strong handle and replace it with a weak handle. - uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target); - gchandle->release(); - gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); + gchandle.release(); + gchandle = weak_gchandle; return false; } @@ -1436,14 +1505,12 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { return refcount == 0; } -CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) { - - CSharpInstance *instance = memnew(CSharpInstance); +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); Reference *ref = Object::cast_to<Reference>(p_owner); - instance->base_ref = ref != NULL; - instance->script = Ref<CSharpScript>(p_script); + instance->base_ref = ref != nullptr; instance->owner = p_owner; instance->gchandle = p_gchandle; @@ -1456,9 +1523,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS } MonoObject *CSharpInstance::get_mono_object() const { - - ERR_FAIL_COND_V(gchandle.is_null(), NULL); - return gchandle->get_target(); + ERR_FAIL_COND_V(gchandle.is_released(), nullptr); + return gchandle.get_target(); } Object *CSharpInstance::get_owner() { @@ -1466,7 +1532,6 @@ Object *CSharpInstance::get_owner() { } bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { - ERR_FAIL_COND_V(!script.is_valid(), false); GD_MONO_SCOPE_THREAD_ATTACH; @@ -1520,7 +1585,6 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { } bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { - ERR_FAIL_COND_V(!script.is_valid(), false); GD_MONO_SCOPE_THREAD_ATTACH; @@ -1542,7 +1606,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { GDMonoProperty *property = top->get_property(p_name); if (property) { - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoObject *value = property->get_value(mono_object, &exc); if (exc) { r_ret = Variant(); @@ -1583,8 +1647,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { return false; } -void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) { - +void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) { List<PropertyInfo> pinfo; get_property_list(&pinfo); @@ -1608,8 +1671,37 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va } } -void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { +void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) { + MonoObject *owner_managed = get_mono_object(); + ERR_FAIL_NULL(owner_managed); + + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); + if (!delegate_field_value) + continue; // Empty + + Array serialized_data; + MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); + + MonoException *exc = nullptr; + bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate_field_value, managed_serialized_data, &exc); + + if (exc) { + GDMonoUtils::debug_print_unhandled_exception(exc); + continue; + } + if (success) { + r_state.push_back(Pair<StringName, Array>(event_signal.field->get_name(), serialized_data)); + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to serialize event signal delegate\n"); + } + } +} + +void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { p_properties->push_back(E->value()); } @@ -1646,7 +1738,6 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { } Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - if (script->member_info.has(p_name)) { if (r_is_valid) *r_is_valid = true; @@ -1660,7 +1751,6 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * } bool CSharpInstance::has_method(const StringName &p_method) const { - if (!script.is_valid()) return false; @@ -1680,7 +1770,6 @@ bool CSharpInstance::has_method(const StringName &p_method) const { } Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - ERR_FAIL_COND_V(!script.is_valid(), Variant()); GD_MONO_SCOPE_THREAD_ATTACH; @@ -1718,7 +1807,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, } void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { - GD_MONO_SCOPE_THREAD_ATTACH; if (script.is_valid()) { @@ -1731,7 +1819,6 @@ void CSharpInstance::call_multilevel(const StringName &p_method, const Variant * } void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) { - GD_MONO_ASSERT_THREAD_ATTACHED; GDMonoClass *top = script->script_class; @@ -1749,17 +1836,15 @@ void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringNam } void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) { - // Sorry, the method is the one that controls the call order call_multilevel(p_method, p_args, p_argcount); } bool CSharpInstance::_reference_owner_unsafe() { - #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); CRASH_COND(unsafe_referenced); // already referenced #endif @@ -1778,10 +1863,9 @@ bool CSharpInstance::_reference_owner_unsafe() { } bool CSharpInstance::_unreference_owner_unsafe() { - #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif if (!unsafe_referenced) @@ -1800,19 +1884,15 @@ bool CSharpInstance::_unreference_owner_unsafe() { } MonoObject *CSharpInstance::_internal_new_managed() { -#ifdef DEBUG_ENABLED - CRASH_COND(!gchandle.is_valid()); -#endif - // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - ERR_FAIL_NULL_V_MSG(ctor, NULL, + ERR_FAIL_NULL_V_MSG(ctor, nullptr, "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - ERR_FAIL_NULL_V(owner, NULL); - ERR_FAIL_COND_V(script.is_null(), NULL); + ERR_FAIL_NULL_V(owner, nullptr); + ERR_FAIL_COND_V(script.is_null(), nullptr); MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); @@ -1824,13 +1904,13 @@ MonoObject *CSharpInstance::_internal_new_managed() { // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. CRASH_COND(die == true); - owner = NULL; + owner = nullptr; - ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); + ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); } // Tie managed to unmanaged - gchandle = MonoGCHandle::create_strong(mono_object); + gchandle = MonoGCHandleData::new_strong_handle(mono_object); if (base_ref) _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) @@ -1838,25 +1918,25 @@ MonoObject *CSharpInstance::_internal_new_managed() { CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); // Construct - ctor->invoke_raw(mono_object, NULL); + ctor->invoke_raw(mono_object, nullptr); return mono_object; } void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { + disconnect_event_signals(); #ifdef DEBUG_ENABLED CRASH_COND(base_ref); - CRASH_COND(gchandle.is_null()); + CRASH_COND(gchandle.is_released()); #endif CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); } void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { - #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(gchandle.is_null()); + CRASH_COND(gchandle.is_released()); #endif r_remove_script_instance = false; @@ -1886,16 +1966,42 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f } } -void CSharpInstance::refcount_incremented() { +void CSharpInstance::connect_event_signals() { + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + StringName signal_name = event_signal.field->get_name(); + + // TODO: Use pooling for ManagedCallable instances. + auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + + owner->connect(signal_name, Callable(event_signal_callable)); + } +} +void CSharpInstance::disconnect_event_signals() { + for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { + const CSharpScript::EventSignal &event_signal = E->value(); + + StringName signal_name = event_signal.field->get_name(); + + // TODO: It would be great if we could store this EventSignalCallable on the stack. + // The problem is that Callable memdeletes it when it's destructed... + auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + + owner->disconnect(signal_name, Callable(event_signal_callable)); + } +} + +void CSharpInstance::refcount_incremented() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); - if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (ref_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // The reference count was increased after the managed side was the only one referencing our owner. @@ -1903,33 +2009,32 @@ void CSharpInstance::refcount_incremented() { // so the owner must hold the managed side alive again to avoid it from being GCed. // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target()); - gchandle->release(); - gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); + MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target()); + gchandle.release(); + gchandle = strong_gchandle; } } bool CSharpInstance::refcount_decremented() { - #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(owner == nullptr); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); int refcount = ref_owner->reference_get_count(); - if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. // Release the current strong handle and replace it with a weak handle. - uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target()); - gchandle->release(); - gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); + MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target()); + gchandle.release(); + gchandle = weak_gchandle; return false; } @@ -1980,7 +2085,6 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab } void CSharpInstance::notification(int p_notification) { - GD_MONO_SCOPE_THREAD_ATTACH; if (p_notification == Object::NOTIFICATION_PREDELETE) { @@ -2005,7 +2109,7 @@ void CSharpInstance::notification(int p_notification) { MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2019,7 +2123,6 @@ void CSharpInstance::notification(int p_notification) { } void CSharpInstance::_call_notification(int p_notification) { - GD_MONO_ASSERT_THREAD_ATTACHED; MonoObject *mono_object = get_mono_object(); @@ -2050,13 +2153,13 @@ String CSharpInstance::to_string(bool *r_valid) { MonoObject *mono_object = get_mono_object(); - if (mono_object == NULL) { + if (mono_object == nullptr) { if (r_valid) *r_valid = false; return String(); } - MonoException *exc = NULL; + MonoException *exc = nullptr; MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); if (exc) { @@ -2066,7 +2169,7 @@ String CSharpInstance::to_string(bool *r_valid) { return String(); } - if (result == NULL) { + if (result == nullptr) { if (r_valid) *r_valid = false; return String(); @@ -2076,42 +2179,34 @@ String CSharpInstance::to_string(bool *r_valid) { } Ref<Script> CSharpInstance::get_script() const { - return script; } ScriptLanguage *CSharpInstance::get_language() { - return CSharpLanguage::get_singleton(); } -CSharpInstance::CSharpInstance() : - owner(NULL), - base_ref(false), - ref_dying(false), - unsafe_referenced(false), - predelete_notified(false), - destructing_script_instance(false) { +CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) : + script(p_script) { } CSharpInstance::~CSharpInstance() { - GD_MONO_SCOPE_THREAD_ATTACH; destructing_script_instance = true; - if (gchandle.is_valid()) { + if (!gchandle.is_released()) { 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) + // we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr) // and that would mess up with the new script instance if called later. - MonoObject *mono_object = gchandle->get_target(); + MonoObject *mono_object = gchandle.get_target(); if (mono_object) { - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2120,7 +2215,7 @@ CSharpInstance::~CSharpInstance() { } } - gchandle->release(); // Make sure the gchandle is released + gchandle.release(); // Make sure the gchandle is released } // If not being called from the owner's destructor, and we still hold a reference to the owner @@ -2141,7 +2236,7 @@ CSharpInstance::~CSharpInstance() { CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); - CRASH_COND(data == NULL); + CRASH_COND(data == nullptr); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); @@ -2177,14 +2272,12 @@ CSharpInstance::~CSharpInstance() { #ifdef TOOLS_ENABLED void CSharpScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { - placeholders.erase(p_placeholder); } #endif #ifdef TOOLS_ENABLED void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames) { - if (base_cache.is_valid()) { base_cache->_update_exports_values(values, propnames); } @@ -2199,7 +2292,6 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List } void CSharpScript::_update_member_info_no_exports() { - if (exports_invalidated) { GD_MONO_ASSERT_THREAD_ATTACHED; @@ -2248,60 +2340,69 @@ void CSharpScript::_update_member_info_no_exports() { #endif bool CSharpScript::_update_exports() { - #ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) - return false; - - placeholder_fallback_enabled = true; // until proven otherwise - + bool is_editor = Engine::get_singleton()->is_editor_hint(); + if (is_editor) + placeholder_fallback_enabled = true; // until proven otherwise +#endif if (!valid) return false; bool changed = false; - if (exports_invalidated) { +#ifdef TOOLS_ENABLED + if (exports_invalidated) +#endif + { GD_MONO_SCOPE_THREAD_ATTACH; - exports_invalidated = false; - changed = true; member_info.clear(); - exported_members_cache.clear(); - exported_members_defval_cache.clear(); - // Here we create a temporary managed instance of the class to get the initial values +#ifdef TOOLS_ENABLED + MonoObject *tmp_object = nullptr; + Object *tmp_native = nullptr; + uint32_t tmp_pinned_gchandle = 0; + + if (is_editor) { + exports_invalidated = false; - MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + exported_members_cache.clear(); + exported_members_defval_cache.clear(); - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } + // Here we create a temporary managed instance of the class to get the initial values + tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); - uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) + if (!tmp_object) { + ERR_PRINT("Failed to allocate temporary MonoObject."); + return false; + } - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) - ERR_FAIL_NULL_V_MSG(ctor, NULL, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - MonoException *ctor_exc = NULL; - ctor->invoke(tmp_object, NULL, &ctor_exc); + ERR_FAIL_NULL_V_MSG(ctor, false, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + MonoException *ctor_exc = nullptr; + ctor->invoke(tmp_object, nullptr, &ctor_exc); - if (ctor_exc) { - // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; + if (ctor_exc) { + // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; + + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(ctor_exc); + return false; + } } +#endif GDMonoClass *top = script_class; @@ -2317,15 +2418,22 @@ bool CSharpScript::_update_exports() { if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { StringName member_name = field->get_name(); + member_info[member_name] = prop_info; + if (exported) { - member_info[member_name] = prop_info; - exported_members_cache.push_front(prop_info); +#ifdef TOOLS_ENABLED + if (is_editor) { + exported_members_cache.push_front(prop_info); - if (tmp_object) { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); + if (tmp_object) { + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); + } } - } else { - member_info[member_name] = prop_info; +#endif + +#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) + exported_members_names.insert(member_name); +#endif } } } @@ -2338,22 +2446,28 @@ bool CSharpScript::_update_exports() { if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { StringName member_name = property->get_name(); + member_info[member_name] = prop_info; + if (exported) { - member_info[member_name] = prop_info; - exported_members_cache.push_front(prop_info); - - if (tmp_object) { - MonoException *exc = NULL; - MonoObject *ret = property->get_value(tmp_object, &exc); - if (exc) { - exported_members_defval_cache[member_name] = Variant(); - GDMonoUtils::debug_print_unhandled_exception(exc); - } else { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); +#ifdef TOOLS_ENABLED + if (is_editor) { + exported_members_cache.push_front(prop_info); + if (tmp_object) { + MonoException *exc = nullptr; + MonoObject *ret = property->get_value(tmp_object, &exc); + if (exc) { + exported_members_defval_cache[member_name] = Variant(); + GDMonoUtils::debug_print_unhandled_exception(exc); + } else { + exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); + } } } - } else { - member_info[member_name] = prop_info; +#endif + +#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) + exported_members_names.insert(member_name); +#endif } } } @@ -2361,52 +2475,57 @@ bool CSharpScript::_update_exports() { top = top->get_parent_class(); } - // Need to check this here, before disposal - bool base_ref = Object::cast_to<Reference>(tmp_native) != NULL; +#ifdef TOOLS_ENABLED + if (is_editor) { + // Need to check this here, before disposal + bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr; - // Dispose the temporary managed instance + // Dispose the temporary managed instance - MonoException *exc = NULL; - GDMonoUtils::dispose(tmp_object, &exc); + MonoException *exc = nullptr; + GDMonoUtils::dispose(tmp_object, &exc); - if (exc) { - ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - } + if (exc) { + ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; + GDMonoUtils::free_gchandle(tmp_pinned_gchandle); + tmp_object = nullptr; - if (tmp_native && !base_ref) { - Node *node = Object::cast_to<Node>(tmp_native); - if (node && node->is_inside_tree()) { - ERR_PRINT("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); + if (tmp_native && !base_ref) { + Node *node = Object::cast_to<Node>(tmp_native); + if (node && node->is_inside_tree()) { + ERR_PRINT("Temporary instance was added to the scene tree."); + } else { + memdelete(tmp_native); + } } } +#endif } - placeholder_fallback_enabled = false; +#ifdef TOOLS_ENABLED + if (is_editor) { + placeholder_fallback_enabled = false; - if (placeholders.size()) { - // Update placeholders if any - Map<StringName, Variant> values; - List<PropertyInfo> propnames; - _update_exports_values(values, propnames); + if (placeholders.size()) { + // Update placeholders if any + Map<StringName, Variant> values; + List<PropertyInfo> propnames; + _update_exports_values(values, propnames); - for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { - E->get()->update(propnames, values); + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } } } +#endif return changed; -#endif - return false; } void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { - // no need to load the script's signals more than once if (!signals_invalidated) { return; @@ -2414,6 +2533,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati // make sure this classes signals are empty when loading for the first time _signals.clear(); + event_signals.clear(); GD_MONO_SCOPE_THREAD_ATTACH; @@ -2421,65 +2541,97 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati while (top && top != p_native_class) { const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); for (int i = delegates.size() - 1; i >= 0; --i) { - Vector<Argument> parameters; - GDMonoClass *delegate = delegates[i]; - if (_get_signal(top, delegate, parameters)) { + if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) + continue; + + // Arguments are accessibles as arguments of .Invoke method + GDMonoMethod *invoke_method = delegate->get_method(mono_get_delegate_invoke(delegate->get_mono_ptr())); + + Vector<SignalParameter> parameters; + if (_get_signal(top, invoke_method, parameters)) { _signals[delegate->get_name()] = parameters; } } + List<StringName> found_event_signals; + + void *iter = nullptr; + MonoEvent *raw_event = nullptr; + while ((raw_event = mono_class_get_events(top->get_mono_ptr(), &iter)) != nullptr) { + MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event); + if (event_attrs) { + if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) { + const char *event_name = mono_event_get_name(raw_event); + found_event_signals.push_back(StringName(event_name)); + } + + mono_custom_attrs_free(event_attrs); + } + } + + const Vector<GDMonoField *> &fields = top->get_all_fields(); + for (int i = 0; i < fields.size(); i++) { + GDMonoField *field = fields[i]; + + GDMonoClass *field_class = field->get_type().type_class; + + if (!mono_class_is_delegate(field_class->get_mono_ptr())) + continue; + + if (!found_event_signals.find(field->get_name())) + continue; + + GDMonoMethod *invoke_method = field_class->get_method(mono_get_delegate_invoke(field_class->get_mono_ptr())); + + Vector<SignalParameter> parameters; + if (_get_signal(top, invoke_method, parameters)) { + event_signals[field->get_name()] = { field, invoke_method, parameters }; + } + } + top = top->get_parent_class(); } signals_invalidated = false; } -bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { +bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms) { GD_MONO_ASSERT_THREAD_ATTACHED; - if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - MonoType *raw_type = p_delegate->get_mono_type(); + Vector<StringName> names; + Vector<ManagedType> types; + p_delegate_invoke->get_parameter_names(names); + p_delegate_invoke->get_parameter_types(types); - if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) { - // Arguments are accessibles as arguments of .Invoke method - GDMonoMethod *invoke = p_delegate->get_method("Invoke", -1); - - Vector<StringName> names; - Vector<ManagedType> types; - invoke->get_parameter_names(names); - invoke->get_parameter_types(types); - - if (names.size() == types.size()) { - for (int i = 0; i < names.size(); ++i) { - Argument arg; - arg.name = names[i]; - arg.type = GDMonoMarshal::managed_to_variant_type(types[i]); - - if (arg.type == Variant::NIL) { - ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); - return false; - } + for (int i = 0; i < names.size(); ++i) { + SignalParameter arg; + arg.name = names[i]; - params.push_back(arg); - } + bool nil_is_variant = false; + arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant); - return true; + if (arg.type == Variant::NIL) { + if (nil_is_variant) { + arg.nil_is_variant = true; + } else { + ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); + return false; } } + + params.push_back(arg); } - return false; + return true; } -#ifdef TOOLS_ENABLED /** * Returns false if there was an error, otherwise true. * If there was an error, r_prop_info and r_exported are not assigned any value. */ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { - GD_MONO_ASSERT_THREAD_ATTACHED; // Goddammit, C++. All I wanted was some nested functions. @@ -2487,8 +2639,10 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) if (p_member->is_static()) { +#ifdef TOOLS_ENABLED if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } @@ -2510,18 +2664,23 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } if (!property->has_setter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } } - Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type); + bool nil_is_variant = false; + Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &nil_is_variant); if (!p_inspect_export || !exported) { r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); @@ -2529,16 +2688,21 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect return true; } +#ifdef TOOLS_ENABLED MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); +#endif PropertyHint hint = PROPERTY_HINT_NONE; String hint_string; - if (variant_type == Variant::NIL) { + if (variant_type == Variant::NIL && !nil_is_variant) { +#ifdef TOOLS_ENABLED ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } +#ifdef TOOLS_ENABLED int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); ERR_FAIL_COND_V_MSG(hint_res == -1, false, @@ -2549,8 +2713,16 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); } +#endif + + uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; + + if (variant_type == Variant::NIL) { + // System.Object (Variant) + prop_usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } - r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); + r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, prop_usage); r_exported = true; return true; @@ -2558,7 +2730,12 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect #undef MEMBER_FULL_QUALIFIED_NAME } +#ifdef TOOLS_ENABLED int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { + if (p_variant_type == Variant::NIL) { + // System.Object (Variant) + return 1; + } GD_MONO_ASSERT_THREAD_ATTACHED; @@ -2592,7 +2769,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage // Instead of using mono_field_get_value_object, we can do this without boxing. Check the // internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field. - MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); + MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, nullptr); ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); @@ -2616,10 +2793,10 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage } } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) { GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); - CRASH_COND(field_native_class == NULL); + CRASH_COND(field_native_class == nullptr); r_hint = PROPERTY_HINT_RESOURCE_TYPE; - r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class); + r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class)); } else if (p_allow_generics && p_variant_type == Variant::ARRAY) { // Nested arrays are not supported in the inspector @@ -2654,18 +2831,16 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage #endif void CSharpScript::_clear() { - tool = false; valid = false; - base = NULL; - native = NULL; - script_class = NULL; + base = nullptr; + native = nullptr; + script_class = nullptr; } Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - - if (unlikely(GDMono::get_singleton() == NULL)) { + if (unlikely(GDMono::get_singleton() == nullptr)) { // Probably not the best error but eh. r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return Variant(); @@ -2679,7 +2854,7 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i GDMonoMethod *method = top->get_method(p_method, p_argcount); if (method && method->is_static()) { - MonoObject *result = method->invoke(NULL, p_args); + MonoObject *result = method->invoke(nullptr, p_args); if (result) { return GDMonoMarshal::mono_object_to_variant(result); @@ -2696,7 +2871,6 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i } void CSharpScript::_resource_path_changed() { - String path = get_path(); if (!path.empty()) { @@ -2705,9 +2879,7 @@ void CSharpScript::_resource_path_changed() { } bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { - if (p_name == CSharpLanguage::singleton->string_names._script_source) { - r_ret = get_source_code(); return true; } @@ -2716,9 +2888,7 @@ bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { } bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == CSharpLanguage::singleton->string_names._script_source) { - set_source_code(p_value); reload(); return true; @@ -2728,20 +2898,17 @@ bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) { } void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const { - p_properties->push_back(PropertyInfo(Variant::STRING, CSharpLanguage::singleton->string_names._script_source, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL)); } void CSharpScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - CRASH_COND(p_class == NULL); + CRASH_COND(p_class == nullptr); // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time Ref<CSharpScript> script = memnew(CSharpScript); @@ -2752,16 +2919,15 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD } void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - CRASH_COND(p_class == NULL); + CRASH_COND(p_class == nullptr); p_script->name = p_class->get_name(); p_script->script_class = p_class; p_script->native = p_native; - CRASH_COND(p_script->native == NULL); + CRASH_COND(p_script->native == nullptr); GDMonoClass *base = p_script->script_class->get_parent_class(); @@ -2816,23 +2982,6 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon } bool CSharpScript::can_instance() const { - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - - // Hack to lower the risk of attached scripts not being added to the C# project - if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted... - if (_create_project_solution_if_needed()) { - CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(), - "Compile", - ProjectSettings::get_singleton()->globalize_path(get_path())); - } else { - ERR_PRINT("C# project could not be created; cannot add file: '" + get_path() + "'."); - } - } - } -#endif - #ifdef TOOLS_ENABLED bool extra_cond = tool || ScriptServer::is_scripting_enabled(); #else @@ -2843,12 +2992,12 @@ bool CSharpScript::can_instance() const { // For tool scripts, this will never fire if the class is not found. That's because we // don't know if it's a tool script if we can't find the class to access the attributes. if (extra_cond && !script_class) { - if (GDMono::get_singleton()->get_project_assembly() == NULL) { + if (GDMono::get_singleton()->get_project_assembly() == nullptr) { // The project assembly is not loaded - ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); + ERR_FAIL_V_MSG(false, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); } else { // The project assembly is loaded, but the class could not found - ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); + ERR_FAIL_V_MSG(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); } } @@ -2856,7 +3005,6 @@ bool CSharpScript::can_instance() const { } StringName CSharpScript::get_instance_base_type() const { - if (native) return native->get_name(); else @@ -2864,20 +3012,19 @@ StringName CSharpScript::get_instance_base_type() const { } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) { - GD_MONO_ASSERT_THREAD_ATTACHED; /* STEP 1, CREATE */ // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - if (ctor == NULL) { - ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL, + if (ctor == nullptr) { + ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr, "Cannot create script instance. The class '" + script_class->get_full_name() + "' does not define a parameterless constructor." + (get_path().empty() ? String() : " Path: '" + get_path() + "'.")); - ERR_FAIL_V_MSG(NULL, "Constructor not found."); + ERR_FAIL_V_MSG(nullptr, "Constructor not found."); } Ref<Reference> ref; @@ -2889,13 +3036,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // If the object had a script instance binding, dispose it before adding the CSharpInstance if (p_owner->has_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index())) { void *data = p_owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); - CRASH_COND(data == NULL); + CRASH_COND(data == nullptr); CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited && script_binding.gchandle.is_valid()) { - MonoObject *mono_object = script_binding.gchandle->get_target(); + if (script_binding.inited && !script_binding.gchandle.is_released()) { + MonoObject *mono_object = script_binding.gchandle.get_target(); if (mono_object) { - MonoException *exc = NULL; + MonoException *exc = nullptr; GDMonoUtils::dispose(mono_object, &exc); if (exc) { @@ -2903,13 +3050,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg } } + script_binding.gchandle.release(); // Just in case script_binding.inited = false; } } - CSharpInstance *instance = memnew(CSharpInstance); + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this))); instance->base_ref = p_isref; - instance->script = Ref<CSharpScript>(this); instance->owner = p_owner; instance->owner->set_script_instance(instance); @@ -2920,19 +3067,19 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg if (!mono_object) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); - instance->owner = NULL; + instance->owner = nullptr; bool die = instance->_unreference_owner_unsafe(); // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. CRASH_COND(die == true); - p_owner->set_script_instance(NULL); + p_owner->set_script_instance(nullptr); r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); + ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); } // Tie managed to unmanaged - instance->gchandle = MonoGCHandle::create_strong(mono_object); + instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object); if (instance->base_ref) instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) @@ -2954,7 +3101,6 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg } Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (!valid) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); @@ -2974,7 +3120,7 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal ref = REF(r); } - CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != NULL, r_error); + CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != nullptr, r_error); if (!instance) { if (ref.is_null()) { memdelete(owner); //no owner, sorry @@ -2990,58 +3136,54 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal } ScriptInstance *CSharpScript::instance_create(Object *p_this) { - #ifdef DEBUG_ENABLED CRASH_COND(!valid); #endif if (native) { - String native_name = NATIVE_GDMONOCLASS_NAME(native); + StringName native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { - if (ScriptDebugger::get_singleton()) { - CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + if (EngineDebugger::is_active()) { + CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, + "Script inherits from native type '" + String(native_name) + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name + - "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); } } GD_MONO_SCOPE_THREAD_ATTACH; Callable::CallError unchecked_error; - return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); + return _create_instance(nullptr, 0, p_this, Object::cast_to<Reference>(p_this) != nullptr, unchecked_error); } PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) { - #ifdef TOOLS_ENABLED PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); placeholders.insert(si); _update_exports(); return si; #else - return NULL; + return nullptr; #endif } bool CSharpScript::instance_has(const Object *p_this) const { - MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); return instances.has((Object *)p_this); } bool CSharpScript::has_source_code() const { - return !source.empty(); } String CSharpScript::get_source_code() const { - return source; } void CSharpScript::set_source_code(const String &p_code) { - if (source == p_code) return; source = p_code; @@ -3051,7 +3193,6 @@ void CSharpScript::set_source_code(const String &p_code) { } void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { - if (!script_class) return; @@ -3065,7 +3206,6 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { } bool CSharpScript::has_method(const StringName &p_method) const { - if (!script_class) return false; @@ -3075,7 +3215,6 @@ bool CSharpScript::has_method(const StringName &p_method) const { } MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { - if (!script_class) return MethodInfo(); @@ -3096,7 +3235,6 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { } Error CSharpScript::reload(bool p_keep_state) { - bool has_instances; { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); @@ -3118,9 +3256,7 @@ Error CSharpScript::reload(bool p_keep_state) { ERR_FAIL_NULL_V(namespace_, ERR_BUG); ERR_FAIL_NULL_V(class_name, ERR_BUG); GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String()); - if (klass) { - bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass); - ERR_FAIL_COND_V(!obj_type, ERR_BUG); + if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) { script_class = klass; } } else { @@ -3128,7 +3264,7 @@ Error CSharpScript::reload(bool p_keep_state) { script_class = project_assembly->get_object_derived_class(name); } - valid = script_class != NULL; + valid = script_class != nullptr; if (script_class) { #ifdef DEBUG_ENABLED @@ -3150,7 +3286,7 @@ Error CSharpScript::reload(bool p_keep_state) { native = GDMonoUtils::get_class_native_base(script_class); - CRASH_COND(native == NULL); + CRASH_COND(native == nullptr); GDMonoClass *base_class = script_class->get_parent_class(); @@ -3258,12 +3394,10 @@ Error CSharpScript::reload(bool p_keep_state) { } ScriptLanguage *CSharpScript::get_language() const { - return CSharpLanguage::get_singleton(); } bool CSharpScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { - #ifdef TOOLS_ENABLED const Map<StringName, Variant>::Element *E = exported_members_defval_cache.find(p_property); @@ -3281,51 +3415,88 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari } void CSharpScript::update_exports() { - #ifdef TOOLS_ENABLED _update_exports(); #endif } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - return _signals.has(p_signal); + return event_signals.has(p_signal) || _signals.has(p_signal); } void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { - for (const Map<StringName, Vector<Argument> >::Element *E = _signals.front(); E; E = E->next()) { + for (const Map<StringName, Vector<SignalParameter>>::Element *E = _signals.front(); E; E = E->next()) { MethodInfo mi; + mi.name = E->key(); + + const Vector<SignalParameter> ¶ms = E->value(); + for (int i = 0; i < params.size(); i++) { + const SignalParameter ¶m = params[i]; + + PropertyInfo arg_info = PropertyInfo(param.type, param.name); + if (param.type == Variant::NIL && param.nil_is_variant) + arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + + mi.arguments.push_back(arg_info); + } + + r_signals->push_back(mi); + } + for (const Map<StringName, EventSignal>::Element *E = event_signals.front(); E; E = E->next()) { + MethodInfo mi; mi.name = E->key(); - for (int i = 0; i < E->get().size(); i++) { - PropertyInfo arg; - arg.name = E->get()[i].name; - mi.arguments.push_back(arg); + + const EventSignal &event_signal = E->value(); + const Vector<SignalParameter> ¶ms = event_signal.parameters; + for (int i = 0; i < params.size(); i++) { + const SignalParameter ¶m = params[i]; + + PropertyInfo arg_info = PropertyInfo(param.type, param.name); + if (param.type == Variant::NIL && param.nil_is_variant) + arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + + mi.arguments.push_back(arg_info); } + r_signals->push_back(mi); } } -Ref<Script> CSharpScript::get_base_script() const { +bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { + Ref<CSharpScript> cs = p_script; + if (cs.is_null()) { + return false; + } + if (script_class == nullptr || cs->script_class == nullptr) { + return false; + } + + if (script_class == cs->script_class) { + return true; + } + + return cs->script_class->is_assignable_from(script_class); +} + +Ref<Script> CSharpScript::get_base_script() const { // TODO search in metadata file once we have it, not important any way? return Ref<Script>(); } void CSharpScript::get_script_property_list(List<PropertyInfo> *p_list) const { - for (Map<StringName, PropertyInfo>::Element *E = member_info.front(); E; E = E->next()) { p_list->push_back(E->value()); } } int CSharpScript::get_member_line(const StringName &p_member) const { - // TODO omnisharp return -1; } MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { - if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) return MultiplayerAPI::RPC_MODE_REMOTE; if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) @@ -3397,7 +3568,6 @@ MultiplayerAPI::RPCMode CSharpScript::get_rset_mode(const StringName &p_variable } Error CSharpScript::load_source_code(const String &p_path) { - Error ferr = read_all_file_utf8(p_path, source); ERR_FAIL_COND_V_MSG(ferr != OK, ferr, @@ -3414,23 +3584,12 @@ Error CSharpScript::load_source_code(const String &p_path) { } StringName CSharpScript::get_script_name() const { - return name; } -CSharpScript::CSharpScript() : - script_list(this) { - +CSharpScript::CSharpScript() { _clear(); -#ifdef TOOLS_ENABLED - source_changed_cache = false; - placeholder_fallback_enabled = false; - exports_invalidated = true; -#endif - - signals_invalidated = true; - _resource_path_changed(); #ifdef DEBUG_ENABLED @@ -3442,17 +3601,25 @@ CSharpScript::CSharpScript() : } CSharpScript::~CSharpScript() { - #ifdef DEBUG_ENABLED MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); #endif } -/*************** RESOURCE ***************/ +void CSharpScript::get_members(Set<StringName> *p_members) { +#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) + if (p_members) { + for (Set<StringName>::Element *E = exported_members_names.front(); E; E = E->next()) { + p_members->insert(E->get()); + } + } +#endif +} -RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress) { +/*************** RESOURCE ***************/ +RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { if (r_error) *r_error = ERR_FILE_CANT_OPEN; @@ -3478,22 +3645,18 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p } void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { - p_extensions->push_back("cs"); } bool ResourceFormatLoaderCSharpScript::handles_type(const String &p_type) const { - return p_type == "Script" || p_type == CSharpLanguage::get_singleton()->get_type(); } String ResourceFormatLoaderCSharpScript::get_resource_type(const String &p_path) const { - return p_path.get_extension().to_lower() == "cs" ? CSharpLanguage::get_singleton()->get_type() : ""; } Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { - Ref<CSharpScript> sqscr = p_resource; ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER); @@ -3537,19 +3700,16 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r } void ResourceFormatSaverCSharpScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { - if (Object::cast_to<CSharpScript>(p_resource.ptr())) { p_extensions->push_back("cs"); } } bool ResourceFormatSaverCSharpScript::recognize(const RES &p_resource) const { - - return Object::cast_to<CSharpScript>(p_resource.ptr()) != NULL; + return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr; } CSharpLanguage::StringNameCache::StringNameCache() { - _signal_callback = StaticCString::create("_signal_callback"); _set = StaticCString::create("_set"); _get = StaticCString::create("_get"); @@ -3559,4 +3719,5 @@ CSharpLanguage::StringNameCache::StringNameCache() { on_before_serialize = StaticCString::create("OnBeforeSerialize"); on_after_deserialize = StaticCString::create("OnAfterDeserialize"); dotctor = StaticCString::create(".ctor"); + delegate_invoke_method_name = StaticCString::create("Invoke"); } |