diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
| -rw-r--r-- | modules/mono/csharp_script.cpp | 1961 |
1 files changed, 1059 insertions, 902 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9e19e5f8c4..c48230f524 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 */ @@ -31,16 +31,19 @@ #include "csharp_script.h" #include <mono/metadata/threads.h> - -#include "core/io/json.h" -#include "core/os/file_access.h" +#include <mono/metadata/tokentype.h> +#include <stdint.h> + +#include "core/config/project_settings.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" +#include "core/io/file_access.h" +#include "core/os/mutex.h" #include "core/os/os.h" #include "core/os/thread.h" -#include "core/project_settings.h" #ifdef TOOLS_ENABLED #include "editor/bindings_generator.h" -#include "editor/csharp_project.h" #include "editor/editor_node.h" #include "editor/node_dock.h" #endif @@ -57,22 +60,19 @@ #include "mono_gd/gd_mono_utils.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" -#include "utils/mutex_utils.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"); } @@ -80,31 +80,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); @@ -129,8 +124,9 @@ void CSharpLanguage::init() { print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); #endif - if (gdmono->is_runtime_initialized()) + if (gdmono->is_runtime_initialized()) { gdmono->initialize_load_assemblies(); + } #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); @@ -138,6 +134,13 @@ void CSharpLanguage::init() { } void CSharpLanguage::finish() { + finalize(); +} + +void CSharpLanguage::finalize() { + if (finalized) { + return; + } finalizing = true; @@ -145,15 +148,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. @@ -161,22 +164,24 @@ void CSharpLanguage::finish() { #ifdef DEBUG_ENABLED for (Map<ObjectID, int>::Element *E = unsafe_object_references.front(); E; E = E->next()) { - const ObjectID &id = E->get(); + const ObjectID &id = E->key(); Object *obj = ObjectDB::get_instance(id); if (obj) { - ERR_PRINTS("Leaked unsafe reference to object: " + obj->get_class() + ":" + itos(id)); + ERR_PRINT("Leaked unsafe reference to object: " + obj->to_string()); } else { - ERR_PRINTS("Leaked unsafe reference to deleted object: " + itos(id)); + ERR_PRINT("Leaked unsafe reference to deleted object: " + itos(id)); } } #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", @@ -287,7 +292,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { "when", "where", "yield", - 0 + nullptr }; const char **w = _reserved_words; @@ -298,21 +303,39 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { } } -void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { +bool CSharpLanguage::is_control_flow_keyword(String p_keyword) const { + return p_keyword == "break" || + p_keyword == "case" || + p_keyword == "catch" || + p_keyword == "continue" || + p_keyword == "default" || + p_keyword == "do" || + p_keyword == "else" || + p_keyword == "finally" || + p_keyword == "for" || + p_keyword == "foreach" || + p_keyword == "goto" || + p_keyword == "if" || + p_keyword == "return" || + p_keyword == "switch" || + p_keyword == "throw" || + p_keyword == "try" || + p_keyword == "while"; +} +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; @@ -321,11 +344,10 @@ 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" - "public class %CLASS% : %BASE%\n" + "public partial class %CLASS% : %BASE%\n" "{\n" " // Declare member variables here. Examples:\n" " // private int a = 2;\n" @@ -344,35 +366,37 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin "// }\n" "}\n"; - String base_class_name = get_base_class_name(p_base_class_name, p_class_name); + // Replaces all spaces in p_class_name with underscores to prevent + // invalid C# Script templates from being generated when the object name + // has spaces in it. + String class_name_no_spaces = p_class_name.replace(" ", "_"); + String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); script_template = script_template.replace("%BASE%", base_class_name) - .replace("%CLASS%", p_class_name); + .replace("%CLASS%", class_name_no_spaces); Ref<CSharpScript> script; - script.instance(); + script.instantiate(); script->set_source_code(script_template); - script->set_name(p_class_name); + script->set_name(class_name_no_spaces); return script; } 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); + String class_name_no_spaces = p_class_name.replace(" ", "_"); + String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces); src = src.replace("%BASE%", base_class_name) - .replace("%CLASS%", p_class_name) + .replace("%CLASS%", class_name_no_spaces) .replace("%TS%", _get_indentation()); p_script->set_source_code(src); } 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); @@ -383,34 +407,32 @@ 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()) + if (p_var_type_name.is_empty()) { return "object"; + } if (!ClassDB::class_exists(p_var_type_name)) { return p_var_type_name; } - if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) + if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) { return "Godot.Object"; + } - if (p_var_type_name == Variant::get_type_name(Variant::REAL)) { + if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) { #ifdef REAL_T_IS_DOUBLE return "double"; #else @@ -418,61 +440,82 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { #endif } - if (p_var_type_name == Variant::get_type_name(Variant::STRING)) + if (p_var_type_name == Variant::get_type_name(Variant::STRING)) { return "string"; // I prefer this one >:[ + } - if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) + if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) { return "Collections.Dictionary"; + } - if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) + if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) { return "Collections.Array"; + } - if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY)) + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) { return "byte[]"; - if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY)) + } + 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::POOL_REAL_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::POOL_STRING_ARRAY)) + 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::POOL_VECTOR2_ARRAY)) + } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { return "Vector2[]"; - if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY)) + } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) { return "Vector3[]"; - if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY)) + } + 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, + Variant::QUATERNION, Variant::AABB, Variant::BASIS, - Variant::TRANSFORM, + Variant::TRANSFORM3D, 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++) { - if (p_var_type_name == Variant::get_type_name(var_types[i])) + if (p_var_type_name == Variant::get_type_name(var_types[i])) { return p_var_type_name; + } } return "object"; } -String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const { +String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const { // FIXME // - Due to Godot's API limitation this just appends the function to the end of the file // - Use fully qualified name if there is ambiguity @@ -480,8 +523,9 @@ String CSharpLanguage::make_function(const String &, const String &p_name, const for (int i = 0; i < p_args.size(); i++) { const String &arg = p_args[i]; - if (i > 0) + if (i > 0) { s += ", "; + } s += variant_type_to_managed_name(arg.get_slice(":", 1)) + " " + escape_csharp_keyword(arg.get_slice(":", 0)); } @@ -490,7 +534,7 @@ String CSharpLanguage::make_function(const String &, const String &p_name, const return s; } #else -String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const { +String CSharpLanguage::make_function(const String &, const String &, const PackedStringArray &) const { return String(); } #endif @@ -515,54 +559,60 @@ 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) + if (_debug_parse_err_line >= 0) { return 1; + } // TODO: StackTrace return 1; } int CSharpLanguage::debug_get_stack_level_line(int p_level) const { - - if (_debug_parse_err_line >= 0) + if (_debug_parse_err_line >= 0) { return _debug_parse_err_line; + } // TODO: StackTrace return 1; } String CSharpLanguage::debug_get_stack_level_function(int p_level) const { - - if (_debug_parse_err_line >= 0) + if (_debug_parse_err_line >= 0) { return String(); + } // TODO: StackTrace return String(); } String CSharpLanguage::debug_get_stack_level_source(int p_level) const { - - if (_debug_parse_err_line >= 0) + if (_debug_parse_err_line >= 0) { return _debug_parse_err_file; + } // TODO: StackTrace return String(); } 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) + if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) { return Vector<StackInfo>(); + } MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); @@ -582,11 +632,17 @@ 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); @@ -597,8 +653,9 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec int frame_count = mono_array_length(frames); - if (frame_count <= 0) + if (frame_count <= 0) { return Vector<StackInfo>(); + } Vector<StackInfo> si; si.resize(frame_count); @@ -632,7 +689,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec void CSharpLanguage::post_unsafe_reference(Object *p_obj) { #ifdef DEBUG_ENABLED - SCOPED_MUTEX_LOCK(unsafe_object_references_lock); + MutexLock lock(unsafe_object_references_lock); ObjectID id = p_obj->get_instance_id(); unsafe_object_references[id]++; #endif @@ -640,25 +697,25 @@ void CSharpLanguage::post_unsafe_reference(Object *p_obj) { void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { #ifdef DEBUG_ENABLED - SCOPED_MUTEX_LOCK(unsafe_object_references_lock); + MutexLock lock(unsafe_object_references_lock); ObjectID id = p_obj->get_instance_id(); Map<ObjectID, int>::Element *elem = unsafe_object_references.find(id); ERR_FAIL_NULL(elem); - if (--elem->value() == 0) + if (--elem->value() == 0) { unsafe_object_references.erase(elem); + } #endif } 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) { @@ -670,11 +727,11 @@ 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) + if (A == B) { return false; // shouldn't happen but.. + } GDMonoClass *I = B->base; while (I) { if (I == A->script_class) { @@ -690,7 +747,6 @@ struct CSharpScriptDepSort { }; void CSharpLanguage::reload_all_scripts() { - #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { GD_MONO_SCOPE_THREAD_ATTACH; @@ -700,7 +756,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()); @@ -719,15 +774,15 @@ 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()) + if (!gdmono->is_runtime_initialized()) { return false; + } GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); String appname = ProjectSettings::get_singleton()->get("application/config/name"); String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.empty()) { + if (appname_safe.is_empty()) { appname_safe = "UnnamedProject"; } @@ -736,34 +791,37 @@ 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)) + if (!FileAccess::exists(proj_asm_path)) { return false; // No assembly to load + } } - if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) + if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) { return false; // Already up to date + } } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) { return false; // No assembly to load + } } return true; } void CSharpLanguage::reload_assemblies(bool p_soft_reload) { - - if (!gdmono->is_runtime_initialized()) + 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; { - SCOPED_MUTEX_LOCK(script_instances_mutex); + MutexLock lock(script_instances_mutex); for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) { // Cast to CSharpScript to avoid being erased by accident @@ -771,30 +829,58 @@ 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<RefCounted>> rc_instances; for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) { CSharpScriptBinding &script_binding = E->value(); - Reference *ref = Object::cast_to<Reference>(script_binding.owner); - if (ref) { - ref_instances.push_back(Ref<Reference>(ref)); + RefCounted *rc = Object::cast_to<RefCounted>(script_binding.owner); + if (rc) { + rc_instances.push_back(Ref<RefCounted>(rc)); } } // 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(); + if (script->get_path().is_empty()) { + script->tied_class_name_for_reload = script->script_class->get_name_for_lookup(); script->tied_class_namespace_for_reload = script->script_class->get_namespace(); } @@ -805,9 +891,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { Object *obj = F->get(); script->pending_reload_instances.insert(obj->get_instance_id()); - Reference *ref = Object::cast_to<Reference>(obj); - if (ref) { - ref_instances.push_back(Ref<Reference>(ref)); + RefCounted *rc = Object::cast_to<RefCounted>(obj); + if (rc) { + rc_instances.push_back(Ref<RefCounted>(rc)); } } @@ -816,9 +902,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { Object *obj = F->get()->get_owner(); script->pending_reload_instances.insert(obj->get_instance_id()); - Reference *ref = Object::cast_to<Reference>(obj); - if (ref) { - ref_instances.push_back(Ref<Reference>(ref)); + RefCounted *rc = Object::cast_to<RefCounted>(obj); + if (rc) { + rc_instances.push_back(Ref<RefCounted>(rc)); } } #endif @@ -834,26 +920,28 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); // Call OnBeforeSerialize - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) - obj->get_script_instance()->call_multilevel(string_names.on_before_serialize); + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { + obj->get_script_instance()->call(string_names.on_before_serialize); + } // Save instance info CSharpScript::StateBackup state; // 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()) { Object *obj = script->instances.front()->get(); - obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload) + obj->set_script(REF()); // Remove script and existing script instances (placeholder are not removed before domain reload) } script->_clear(); @@ -863,21 +951,20 @@ 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()) { Object *obj = ObjectDB::get_instance(F->key()); - if (!obj) + if (!obj) { continue; + } ObjectID obj_id = obj->get_instance_id(); // Use a placeholder for now to avoid losing the state when saving a scene - obj->set_script(scr.get_ref_ptr()); - PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj); obj->set_script_instance(placeholder); @@ -888,8 +975,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); @@ -899,19 +986,18 @@ 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()) { #ifdef TOOLS_ENABLED - script->exports_invalidated = true; + script->exports_invalidated = true; #endif - script->signals_invalidated = true; + script->signals_invalidated = true; + if (!script->get_path().is_empty()) { script->reload(p_soft_reload); - script->update_exports(); if (!script->valid) { script->pending_reload_instances.clear(); @@ -923,12 +1009,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 @@ -954,7 +1040,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()) { @@ -999,17 +1085,17 @@ 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.get_ref_ptr()); // will create the script instance as well + obj->set_script(script); // will create the script instance as well } } 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()) { @@ -1027,19 +1113,85 @@ 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(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()) { @@ -1050,70 +1202,75 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::_load_scripts_metadata() { +void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { + if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { + return; + } - scripts_metadata.clear(); + MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); + String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); - String scripts_metadata_filename = "scripts_metadata."; + dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( + p_class->get_namespace(), p_class->get_name(), p_class); +} -#ifdef TOOLS_ENABLED - scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player"; -#else -#ifdef DEBUG_ENABLED - scripts_metadata_filename += "debug"; -#else - scripts_metadata_filename += "release"; -#endif -#endif +void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { + if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { + MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); + bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); + if (requires_lookup) { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + MonoImage *image = p_assembly->get_image(); - if (FileAccess::exists(scripts_metadata_path)) { - String old_json; + int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + for (int i = 1; i < rows; i++) { + // We don't search inner classes, only top-level. + MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - ERR_FAIL_COND(ferr != OK); + if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { + continue; + } - Variant old_dict_var; - String err_str; - int err_line; - Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); - if (json_err != OK) { - ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); - return; - } + GDMonoClass *current = p_assembly->get_class(mono_class); + if (current) { + lookup_script_for_class(current); + } + } + } else { + // This is the most likely scenario as we use C# source generators + MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - scripts_metadata = old_dict_var.operator Dictionary(); - scripts_metadata_invalidated = false; + int length = mono_array_length(script_types); - print_verbose("Successfully loaded scripts metadata"); - } else { - if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file."); + for (int i = 0; i < length; i++) { + MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); + ManagedType type = ManagedType::from_reftype(reftype); + ERR_CONTINUE(!type.type_class); + lookup_script_for_class(type.type_class); + } } } } 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(); @@ -1122,7 +1279,6 @@ void CSharpLanguage::thread_enter() { } void CSharpLanguage::thread_exit() { - #if 0 if (gdmono->is_runtime_initialized()) { GDMonoUtils::detach_current_thread(); @@ -1131,13 +1287,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; @@ -1145,12 +1300,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; @@ -1160,31 +1314,44 @@ 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; } - scripts_metadata_invalidated = true; +#ifdef GD_MONO_HOT_RELOAD + { + 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; + } + } +#endif + + dotnet_script_lookup_map.clear(); } #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); + EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>( + GDMonoMarshal::mono_object_to_variant(mono_object).operator Object *()); + CRASH_COND(godotsharp_editor == nullptr); // Enable it as a plugin EditorNode::add_editor_plugin(godotsharp_editor); @@ -1195,112 +1362,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 - SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex); - p_gchandle->release(); +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(); } } -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 - SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex); + 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; - -#ifdef NO_THREADS - script_instances_mutex = NULL; - script_gchandle_release_mutex = NULL; - language_bind_mutex = NULL; -#else - script_instances_mutex = Mutex::create(); - script_gchandle_release_mutex = Mutex::create(); - language_bind_mutex = Mutex::create(); -#endif - -#ifdef DEBUG_ENABLED -#ifdef NO_THREADS - unsafe_object_references_lock = NULL; -#else - unsafe_object_references_lock = Mutex::create(); -#endif -#endif - - lang_idx = -1; - - scripts_metadata_invalidated = true; - -#ifdef TOOLS_ENABLED - godotsharp_editor = NULL; -#endif } CSharpLanguage::~CSharpLanguage() { - - finish(); - - if (script_instances_mutex) { - memdelete(script_instances_mutex); - script_instances_mutex = NULL; - } - - if (language_bind_mutex) { - memdelete(language_bind_mutex); - language_bind_mutex = NULL; - } - - if (script_gchandle_release_mutex) { - memdelete(script_gchandle_release_mutex); - script_gchandle_release_mutex = NULL; - } - -#ifdef DEBUG_ENABLED - if (unsafe_object_references_lock) { - memdelete(unsafe_object_references_lock); - unsafe_object_references_lock = NULL; - } -#endif - - singleton = NULL; + finalize(); + 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 @@ -1308,8 +1416,9 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b // ¯\_(ツ)_/¯ const ClassDB::ClassInfo *classinfo = ClassDB::classes.getptr(type_name); - while (classinfo && !classinfo->exposed) + while (classinfo && !classinfo->exposed) { classinfo = classinfo->inherits_ptr; + } ERR_FAIL_NULL_V(classinfo, false); type_name = classinfo->name; @@ -1324,63 +1433,63 @@ 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 - Reference *ref = Object::cast_to<Reference>(p_object); + RefCounted *rc = Object::cast_to<RefCounted>(p_object); - if (ref) { + if (rc) { // Unsafe refcount increment. The managed instance also counts as a reference. // This way if the unmanaged world has no references to our owner // but the managed instance is alive, the refcount will be 1 instead of 0. - // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - ref->reference(); - CSharpLanguage::get_singleton()->post_unsafe_reference(ref); + rc->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); } return true; } void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { - - SCOPED_MUTEX_LOCK(language_bind_mutex); + MutexLock lock(language_bind_mutex); Map<Object *, CSharpScriptBinding>::Element *match = script_bindings.find(p_object); - if (match) + if (match) { return (void *)match; + } CSharpScriptBinding script_binding; - if (!setup_csharp_script_binding(script_binding, p_object)) - return NULL; + if (!setup_csharp_script_binding(script_binding, p_object)) { + 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()); + CRASH_COND(!script_bindings.is_empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; } - if (finalizing) + if (finalizing) { return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there + } GD_MONO_ASSERT_THREAD_ATTACHED; { - SCOPED_MUTEX_LOCK(language_bind_mutex); + MutexLock lock(language_bind_mutex); Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data; @@ -1389,10 +1498,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); @@ -1400,11 +1510,10 @@ 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); + RefCounted *rc_owner = Object::cast_to<RefCounted>(p_object); #ifdef DEBUG_ENABLED - CRASH_COND(!ref_owner); + CRASH_COND(!rc_owner); CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif @@ -1412,35 +1521,36 @@ 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) + 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 (rc_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(); - if (!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); + RefCounted *rc_owner = Object::cast_to<RefCounted>(p_object); #ifdef DEBUG_ENABLED - CRASH_COND(!ref_owner); + CRASH_COND(!rc_owner); CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif @@ -1448,27 +1558,29 @@ 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(); + int refcount = rc_owner->reference_get_count(); - if (!script_binding.inited) + 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(); - if (!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; } @@ -1476,19 +1588,18 @@ 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 *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - CSharpInstance *instance = memnew(CSharpInstance); + RefCounted *rc = Object::cast_to<RefCounted>(p_owner); - Reference *ref = Object::cast_to<Reference>(p_owner); - - instance->base_ref = ref != NULL; - instance->script = Ref<CSharpScript>(p_script); + instance->base_ref_counted = rc != nullptr; instance->owner = p_owner; instance->gchandle = p_gchandle; - if (instance->base_ref) + if (instance->base_ref_counted) { instance->_reference_owner_unsafe(); + } p_script->instances.insert(p_owner); @@ -1496,9 +1607,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() { @@ -1506,7 +1616,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; @@ -1547,8 +1656,9 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { MonoObject *ret = method->invoke(mono_object, args); - if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) + if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) { return true; + } break; } @@ -1560,7 +1670,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; @@ -1582,7 +1691,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(); @@ -1623,8 +1732,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); @@ -1635,8 +1743,9 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va ManagedType managedType; GDMonoField *field = script->script_class->get_field(state_pair.first); - if (!field) + if (!field) { continue; // Properties ignored. We get the property baking fields instead. + } managedType = field->get_type(); @@ -1648,8 +1757,38 @@ 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()); } @@ -1673,8 +1812,9 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { if (ret) { Array array = Array(GDMonoMarshal::mono_object_to_variant(ret)); - for (int i = 0, size = array.size(); i < size; i++) + for (int i = 0, size = array.size(); i < size; i++) { p_properties->push_back(PropertyInfo::from_dict(array.get(i))); + } return; } @@ -1686,23 +1826,24 @@ 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) + if (r_is_valid) { *r_is_valid = true; + } return script->member_info[p_name].type; } - if (r_is_valid) + if (r_is_valid) { *r_is_valid = false; + } return Variant::NIL; } bool CSharpInstance::has_method(const StringName &p_method) const { - - if (!script.is_valid()) + if (!script.is_valid()) { return false; + } GD_MONO_SCOPE_THREAD_ATTACH; @@ -1719,17 +1860,15 @@ bool CSharpInstance::has_method(const StringName &p_method) const { return false; } -Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - - if (!script.is_valid()) - ERR_FAIL_V(Variant()); +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; MonoObject *mono_object = get_mono_object(); if (!mono_object) { - r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; ERR_FAIL_V(Variant()); } @@ -1741,7 +1880,7 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, if (method) { MonoObject *return_value = method->invoke(mono_object, p_args); - r_error.error = Variant::CallError::CALL_OK; + r_error.error = Callable::CallError::CALL_OK; if (return_value) { return GDMonoMarshal::mono_object_to_variant(return_value); @@ -1753,54 +1892,15 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, top = top->get_parent_class(); } - r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } -void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { - - GD_MONO_SCOPE_THREAD_ATTACH; - - if (script.is_valid()) { - MonoObject *mono_object = get_mono_object(); - - ERR_FAIL_NULL(mono_object); - - _call_multilevel(mono_object, p_method, p_args, p_argcount); - } -} - -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; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); - - if (method) { - method->invoke(p_mono_object, p_args); - return; - } - - top = top->get_parent_class(); - } -} - -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(!base_ref_counted); + CRASH_COND(owner == nullptr); CRASH_COND(unsafe_referenced); // already referenced #endif @@ -1810,7 +1910,7 @@ bool CSharpInstance::_reference_owner_unsafe() { // See: _unreference_owner_unsafe() // May not me referenced yet, so we must use init_ref() instead of reference() - if (static_cast<Reference *>(owner)->init_ref()) { + if (static_cast<RefCounted *>(owner)->init_ref()) { CSharpLanguage::get_singleton()->post_unsafe_reference(owner); unsafe_referenced = true; } @@ -1819,14 +1919,14 @@ bool CSharpInstance::_reference_owner_unsafe() { } bool CSharpInstance::_unreference_owner_unsafe() { - #ifdef DEBUG_ENABLED - CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(!base_ref_counted); + CRASH_COND(owner == nullptr); #endif - if (!unsafe_referenced) + if (!unsafe_referenced) { return false; // Already unreferenced + } unsafe_referenced = false; @@ -1837,23 +1937,19 @@ bool CSharpInstance::_unreference_owner_unsafe() { // Destroying the owner here means self destructing, so we defer the owner destruction to the caller. CSharpLanguage::get_singleton()->pre_unsafe_unreference(owner); - return static_cast<Reference *>(owner)->unreference(); + return static_cast<RefCounted *>(owner)->unreference(); } 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()); @@ -1863,43 +1959,48 @@ MonoObject *CSharpInstance::_internal_new_managed() { bool die = _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); + CRASH_COND(die); - 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) + if (base_ref_counted) { _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + } 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) { + // Must make sure event signals are not left dangling + disconnect_event_signals(); #ifdef DEBUG_ENABLED - CRASH_COND(base_ref); - CRASH_COND(gchandle.is_null()); + CRASH_COND(base_ref_counted); + 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(!base_ref_counted); + CRASH_COND(gchandle.is_released()); #endif + // Must make sure event signals are not left dangling + disconnect_event_signals(); + r_remove_script_instance = false; if (_unreference_owner_unsafe()) { @@ -1927,16 +2028,40 @@ 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. + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); + } +} + +void CSharpInstance::disconnect_event_signals() { + for (const List<Callable>::Element *E = connected_event_signals.front(); E; E = E->next()) { + const Callable &callable = E->get(); + const EventSignalCallable *event_signal_callable = static_cast<const EventSignalCallable *>(callable.get_custom()); + owner->disconnect(event_signal_callable->get_signal(), callable); + } + + connected_event_signals.clear(); +} + +void CSharpInstance::refcount_incremented() { #ifdef DEBUG_ENABLED - CRASH_COND(!base_ref); - CRASH_COND(owner == NULL); + CRASH_COND(!base_ref_counted); + CRASH_COND(owner == nullptr); #endif - Reference *ref_owner = Object::cast_to<Reference>(owner); + RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); - if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (rc_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. @@ -1944,33 +2069,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(!base_ref_counted); + CRASH_COND(owner == nullptr); #endif - Reference *ref_owner = Object::cast_to<Reference>(owner); + RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); - int refcount = ref_owner->reference_get_count(); + int refcount = rc_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,71 +2104,11 @@ bool CSharpInstance::refcount_decremented() { return ref_dying; } -MultiplayerAPI::RPCMode CSharpInstance::_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))) - return MultiplayerAPI::RPC_MODE_MASTER; - if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) - return MultiplayerAPI::RPC_MODE_PUPPET; - if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute))) - return MultiplayerAPI::RPC_MODE_PUPPET; - if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) - return MultiplayerAPI::RPC_MODE_REMOTESYNC; - if (p_member->has_attribute(CACHED_CLASS(SyncAttribute))) - return MultiplayerAPI::RPC_MODE_REMOTESYNC; - if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) - return MultiplayerAPI::RPC_MODE_MASTERSYNC; - if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) - return MultiplayerAPI::RPC_MODE_PUPPETSYNC; - - return MultiplayerAPI::RPC_MODE_DISABLED; -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_fetched_method_unknown_params(p_method); - - if (method && !method->is_static()) - return _member_get_rpc_mode(method); - - top = top->get_parent_class(); - } - - return MultiplayerAPI::RPC_MODE_DISABLED; -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_variable); - - if (field && !field->is_static()) - return _member_get_rpc_mode(field); - - GDMonoProperty *property = top->get_property(p_variable); - - if (property && !property->is_static()) - return _member_get_rpc_mode(property); - - top = top->get_parent_class(); - } - - return MultiplayerAPI::RPC_MODE_DISABLED; +const Vector<MultiplayerAPI::RPCConfig> CSharpInstance::get_rpc_methods() const { + return script->get_rpc_methods(); } void CSharpInstance::notification(int p_notification) { - GD_MONO_SCOPE_THREAD_ATTACH; if (p_notification == Object::NOTIFICATION_PREDELETE) { @@ -2054,12 +2118,12 @@ void CSharpInstance::notification(int p_notification) { predelete_notified = true; - if (base_ref) { - // It's not safe to proceed if the owner derives Reference and the refcount reached 0. + if (base_ref_counted) { + // It's not safe to proceed if the owner derives RefCounted and the refcount reached 0. // At this point, Dispose() was already called (manually or from the finalizer) so // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the // managed side references it and Dispose() needs to be called to release it. - // However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but + // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 return; } @@ -2069,7 +2133,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) { @@ -2083,7 +2147,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(); @@ -2091,7 +2154,7 @@ void CSharpInstance::_call_notification(int p_notification) { // Custom version of _call_multilevel, optimized for _notification - uint32_t arg = p_notification; + int32_t arg = p_notification; void *args[1] = { &arg }; StringName method_name = CACHED_STRING_NAME(_notification); @@ -2114,25 +2177,28 @@ String CSharpInstance::to_string(bool *r_valid) { MonoObject *mono_object = get_mono_object(); - if (mono_object == NULL) { - if (r_valid) + 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) { GDMonoUtils::set_pending_exception(exc); - if (r_valid) + if (r_valid) { *r_valid = false; + } return String(); } - if (result == NULL) { - if (r_valid) + if (result == nullptr) { + if (r_valid) { *r_valid = false; + } return String(); } @@ -2140,42 +2206,37 @@ 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()) { + // Must make sure event signals are not left dangling + disconnect_event_signals(); + + 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) { @@ -2184,33 +2245,33 @@ 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 - if (base_ref && !ref_dying && owner && unsafe_referenced) { + if (base_ref_counted && !ref_dying && owner && unsafe_referenced) { // The owner's script or script instance is being replaced (or removed) // Transfer ownership to an "instance binding" - Reference *ref_owner = static_cast<Reference *>(owner); + RefCounted *rc_owner = static_cast<RefCounted *>(owner); // We will unreference the owner before referencing it again, so we need to keep it alive - Ref<Reference> scope_keep_owner_alive(ref_owner); + Ref<RefCounted> scope_keep_owner_alive(rc_owner); (void)scope_keep_owner_alive; // Unreference the owner here, before the new "instance binding" references it. // Otherwise, the unsafe reference debug checks will incorrectly detect a bug. bool die = _unreference_owner_unsafe(); - CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die + CRASH_COND(die); // `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(); if (!script_binding.inited) { - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); if (!script_binding.inited) { // Other thread may have set it up // Already had a binding that needs to be setup @@ -2221,12 +2282,12 @@ CSharpInstance::~CSharpInstance() { #ifdef DEBUG_ENABLED // The "instance binding" holds a reference so the refcount should be at least 2 before `scope_keep_owner_alive` goes out of scope - CRASH_COND(ref_owner->reference_get_count() <= 1); + CRASH_COND(rc_owner->reference_get_count() <= 1); #endif } if (script.is_valid() && owner) { - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); #ifdef DEBUG_ENABLED // CSharpInstance must not be created unless it's going to be added to the list for sure @@ -2241,14 +2302,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); } @@ -2263,7 +2322,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; @@ -2311,61 +2369,72 @@ void CSharpScript::_update_member_info_no_exports() { } #endif -bool CSharpScript::_update_exports() { - +bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) { #ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) - return false; - - placeholder_fallback_enabled = true; // until proven otherwise - - if (!valid) + 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; - MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + if (is_editor) { + exports_invalidated = false; - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } + exported_members_cache.clear(); + exported_members_defval_cache.clear(); - uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) + // 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()); - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + if (!tmp_object) { + ERR_PRINT("Failed to allocate temporary MonoObject."); + return false; + } - ERR_FAIL_NULL_V_MSG(ctor, NULL, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); + tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) - MonoException *ctor_exc = NULL; - ctor->invoke(tmp_object, NULL, &ctor_exc); + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + ERR_FAIL_NULL_V_MSG(ctor, false, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - if (ctor_exc) { - // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + MonoException *ctor_exc = nullptr; + ctor->invoke(tmp_object, nullptr, &ctor_exc); - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; + tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; + if (ctor_exc) { + // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + + 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; @@ -2381,15 +2450,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 } } } @@ -2402,22 +2478,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 } } } @@ -2425,52 +2507,61 @@ 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_counted = Object::cast_to<RefCounted>(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_PRINTS("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); + if (tmp_native && !base_ref_counted) { + 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; - - 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); +#ifdef TOOLS_ENABLED + if (is_editor) { + placeholder_fallback_enabled = false; + + if ((changed || p_instance_to_update) && placeholders.size()) { + // Update placeholders if any + Map<StringName, Variant> values; + List<PropertyInfo> propnames; + _update_exports_values(values, propnames); + + if (changed) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } + } else { + p_instance_to_update->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; @@ -2478,6 +2569,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; @@ -2485,65 +2577,100 @@ 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())) { + String event_name = String::utf8(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_PRINTS("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. @@ -2551,13 +2678,17 @@ 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()) { - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) - ERR_PRINTS("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#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; } - if (member_info.has(p_member->get_name())) + if (member_info.has(p_member->get_name())) { return false; + } ManagedType type; @@ -2574,18 +2705,25 @@ 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()) { - if (exported) - ERR_PRINTS("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#ifdef TOOLS_ENABLED + if (exported) { + ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + } +#endif return false; } if (!property->has_setter()) { - if (exported) - ERR_PRINTS("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#ifdef TOOLS_ENABLED + if (exported) { + ERR_PRINT("Cannot export a property without a setter: '" + 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); @@ -2593,16 +2731,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) { - ERR_PRINTS("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + 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, @@ -2613,8 +2756,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; @@ -2622,7 +2773,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; @@ -2648,7 +2804,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage name_only_hint_string += ","; } - String enum_field_name = mono_field_get_name(field); + String enum_field_name = String::utf8(mono_field_get_name(field)); r_hint_string += enum_field_name; name_only_hint_string += enum_field_name; @@ -2656,7 +2812,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."); @@ -2680,17 +2836,18 @@ 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 ManagedType elem_type; - if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) + if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) { return 0; + } Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type); @@ -2717,21 +2874,10 @@ 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; -} - -Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - - if (unlikely(GDMono::get_singleton() == NULL)) { +Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (unlikely(GDMono::get_singleton() == nullptr)) { // Probably not the best error but eh. - r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return Variant(); } @@ -2743,7 +2889,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); @@ -2760,18 +2906,11 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i } void CSharpScript::_resource_path_changed() { - - String path = get_path(); - - if (!path.empty()) { - name = get_path().get_file().get_basename(); - } + _update_name(); } 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; } @@ -2780,9 +2919,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; @@ -2792,20 +2929,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); @@ -2816,23 +2950,34 @@ 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); + + p_script->valid = true; + + update_script_class_info(p_script); + +#ifdef TOOLS_ENABLED + p_script->_update_member_info_no_exports(); +#endif +} +// Extract information about the script using the mono class. +void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { GDMonoClass *base = p_script->script_class->get_parent_class(); - if (base != p_script->native) + // `base` should only be set if the script is a user defined type. + if (base != p_script->native) { p_script->base = base; + } - p_script->valid = true; p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); if (!p_script->tool) { @@ -2840,7 +2985,7 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); } -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED if (!p_script->tool) { p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); } @@ -2856,8 +3001,9 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon while (native_top) { native_top->fetch_methods_with_godot_api_checks(p_script->native); - if (native_top == CACHED_CLASS(GodotObject)) + if (native_top == CACHED_CLASS(GodotObject)) { break; + } native_top = native_top->get_parent_class(); } @@ -2866,37 +3012,44 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); - // Need to fetch method from base classes as well + p_script->rpc_functions.clear(); + GDMonoClass *top = p_script->script_class; while (top && top != p_script->native) { + // Fetch methods from base classes as well top->fetch_methods_with_godot_api_checks(p_script->native); + + // Update RPC info + { + Vector<GDMonoMethod *> methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); i++) { + if (!methods[i]->is_static()) { + MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]); + if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { + MultiplayerAPI::RPCConfig nd; + nd.name = methods[i]->get_name(); + nd.rpc_mode = mode; + // TODO Transfer mode, channel + nd.transfer_mode = NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE; + nd.channel = 0; + if (-1 == p_script->rpc_functions.find(nd)) { + p_script->rpc_functions.push_back(nd); + } + } + } + } + } + top = top->get_parent_class(); } + // Sort so we are 100% that they are always the same. + p_script->rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); + p_script->load_script_signals(p_script->script_class, p_script->native); -#ifdef TOOLS_ENABLED - p_script->_update_member_info_no_exports(); -#endif } -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_PRINTS("C# project could not be created; cannot add file: '" + get_path() + "'."); - } - } - } -#endif - +bool CSharpScript::can_instantiate() const { #ifdef TOOLS_ENABLED bool extra_cond = tool || ScriptServer::is_scripting_enabled(); #else @@ -2907,12 +3060,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() + "'."); } } @@ -2920,46 +3073,45 @@ bool CSharpScript::can_instance() const { } StringName CSharpScript::get_instance_base_type() const { - - if (native) + if (native) { return native->get_name(); - else + } else { return StringName(); + } } -CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) { - +CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, 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() + "'.")); + (get_path().is_empty() ? String() : " Path: '" + get_path() + "'.")); - ERR_FAIL_V_MSG(NULL, "Constructor not found."); + ERR_FAIL_V_MSG(nullptr, "Constructor not found."); } - Ref<Reference> ref; - if (p_isref) { + Ref<RefCounted> ref; + if (p_is_ref_counted) { // Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance. - ref = Ref<Reference>(static_cast<Reference *>(p_owner)); + ref = Ref<RefCounted>(static_cast<RefCounted *>(p_owner)); } // 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) { @@ -2967,13 +3119,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); - instance->base_ref = p_isref; - instance->script = Ref<CSharpScript>(this); + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this))); + instance->base_ref_counted = p_is_ref_counted; instance->owner = p_owner; instance->owner->set_script_instance(instance); @@ -2984,25 +3136,26 @@ 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); + CRASH_COND(die); - p_owner->set_script_instance(NULL); - r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); + p_owner->set_script_instance(nullptr); + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + 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) + if (instance->base_ref_counted) { instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + } { - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); instances.insert(instance->owner); } @@ -3017,28 +3170,27 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg return instance; } -Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { - +Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (!valid) { - r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } - r_error.error = Variant::CallError::CALL_OK; + r_error.error = Callable::CallError::CALL_OK; ERR_FAIL_NULL_V(native, Variant()); GD_MONO_SCOPE_THREAD_ATTACH; - Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instantiate(NATIVE_GDMONOCLASS_NAME(native)); REF ref; - Reference *r = Object::cast_to<Reference>(owner); + RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { 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 @@ -3054,60 +3206,57 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call } 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 instantiated 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 instantiated in object of type: '" + p_this->get_class() + "'."); } } GD_MONO_SCOPE_THREAD_ATTACH; - Variant::CallError unchecked_error; - return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); + Callable::CallError unchecked_error; + return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(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(); + _update_exports(si); return si; #else - return NULL; + return nullptr; #endif } bool CSharpScript::instance_has(const Object *p_this) const { - - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); return instances.has((Object *)p_this); } bool CSharpScript::has_source_code() const { - - return !source.empty(); + return !source.is_empty(); } String CSharpScript::get_source_code() const { - return source; } void CSharpScript::set_source_code(const String &p_code) { - - if (source == p_code) + if (source == p_code) { return; + } source = p_code; #ifdef TOOLS_ENABLED source_changed_cache = true; @@ -3115,9 +3264,9 @@ void CSharpScript::set_source_code(const String &p_code) { } void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { - - if (!script_class) + if (!script_class) { return; + } GD_MONO_SCOPE_THREAD_ATTACH; @@ -3129,9 +3278,9 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { } bool CSharpScript::has_method(const StringName &p_method) const { - - if (!script_class) + if (!script_class) { return false; + } GD_MONO_SCOPE_THREAD_ATTACH; @@ -3139,9 +3288,9 @@ bool CSharpScript::has_method(const StringName &p_method) const { } MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { - - if (!script_class) + if (!script_class) { return MethodInfo(); + } GD_MONO_SCOPE_THREAD_ATTACH; @@ -3160,10 +3309,9 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { } Error CSharpScript::reload(bool p_keep_state) { - bool has_instances; { - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); has_instances = instances.size(); } @@ -3171,100 +3319,41 @@ Error CSharpScript::reload(bool p_keep_state) { GD_MONO_SCOPE_THREAD_ATTACH; - GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); - - if (project_assembly) { - const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path()); - if (script_metadata_var) { - Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"]; - const Variant *namespace_ = script_metadata.getptr("namespace"); - const Variant *class_name = script_metadata.getptr("class_name"); - 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); - script_class = klass; - } - } else { - // Missing script metadata. Fallback to legacy method - script_class = project_assembly->get_object_derived_class(name); - } - - valid = script_class != NULL; - - if (script_class) { -#ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); -#endif - - tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); - - if (!tool) { - GDMonoClass *nesting_class = script_class->get_nesting_class(); - tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); - } - -#if TOOLS_ENABLED - if (!tool) { - tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); - } -#endif - - native = GDMonoUtils::get_class_native_base(script_class); + const DotNetScriptLookupInfo *lookup_info = + CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); - CRASH_COND(native == NULL); - - GDMonoClass *base_class = script_class->get_parent_class(); + if (lookup_info) { + GDMonoClass *klass = lookup_info->script_class; + if (klass) { + ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); + script_class = klass; + } + } - if (base_class != native) - base = base_class; + valid = script_class != nullptr; + if (script_class) { #ifdef DEBUG_ENABLED - // For debug builds, we must fetch from all native base methods as well. - // Native base methods must be fetched before the current class. - // Not needed if the script class itself is a native class. - - if (script_class != native) { - GDMonoClass *native_top = native; - while (native_top) { - native_top->fetch_methods_with_godot_api_checks(native); - - if (native_top == CACHED_CLASS(GodotObject)) - break; - - native_top = native_top->get_parent_class(); - } - } + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif - script_class->fetch_methods_with_godot_api_checks(native); + native = GDMonoUtils::get_class_native_base(script_class); - // Need to fetch method from base classes as well - GDMonoClass *top = script_class; - while (top && top != native) { - top->fetch_methods_with_godot_api_checks(native); - top = top->get_parent_class(); - } + CRASH_COND(native == nullptr); - load_script_signals(script_class, native); - _update_exports(); - } + update_script_class_info(this); - return OK; + _update_exports(); } - return ERR_FILE_MISSING_DEPENDENCIES; + return OK; } 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); @@ -3282,58 +3371,124 @@ 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; } -Error CSharpScript::load_source_code(const String &p_path) { +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))) { + return MultiplayerAPI::RPC_MODE_MASTER; + } + if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) { + return MultiplayerAPI::RPC_MODE_PUPPET; + } + if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) { + return MultiplayerAPI::RPC_MODE_REMOTESYNC; + } + if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) { + return MultiplayerAPI::RPC_MODE_MASTERSYNC; + } + if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) { + return MultiplayerAPI::RPC_MODE_PUPPETSYNC; + } + + return MultiplayerAPI::RPC_MODE_DISABLED; +} +const Vector<MultiplayerAPI::RPCConfig> CSharpScript::get_rpc_methods() const { + return rpc_functions; +} + +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, ferr == ERR_INVALID_DATA ? - "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded." + "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded." " Please ensure that scripts are saved in valid UTF-8 unicode." : - "Failed to read file: '" + p_path + "'."); + "Failed to read file: '" + p_path + "'."); #ifdef TOOLS_ENABLED source_changed_cache = true; @@ -3342,48 +3497,59 @@ Error CSharpScript::load_source_code(const String &p_path) { return OK; } -StringName CSharpScript::get_script_name() const { +void CSharpScript::_update_name() { + String path = get_path(); - return name; + if (!path.is_empty()) { + name = get_path().get_file().get_basename(); + } } -CSharpScript::CSharpScript() : - script_list(this) { - - _clear(); +void CSharpScript::_clear() { + tool = false; + valid = false; -#ifdef TOOLS_ENABLED - source_changed_cache = false; - placeholder_fallback_enabled = false; - exports_invalidated = true; -#endif + base = nullptr; + native = nullptr; + script_class = nullptr; +} - signals_invalidated = true; +CSharpScript::CSharpScript() { + _clear(); - _resource_path_changed(); + _update_name(); #ifdef DEBUG_ENABLED { - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); CSharpLanguage::get_singleton()->script_list.add(&this->script_list); } #endif } CSharpScript::~CSharpScript() { - #ifdef DEBUG_ENABLED - SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex); + 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) { +/*************** RESOURCE ***************/ - if (r_error) +RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + if (r_error) { *r_error = ERR_FILE_CANT_OPEN; + } // TODO ignore anything inside bin/ and obj/ in tools builds? @@ -3400,29 +3566,26 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p script->reload(); - if (r_error) + if (r_error) { *r_error = OK; + } return scriptres; } 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); @@ -3430,14 +3593,10 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r #ifdef TOOLS_ENABLED if (!FileAccess::exists(p_path)) { - // The file does not yet exists, let's assume the user just created this script - - if (_create_project_solution_if_needed()) { - CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(), - "Compile", - ProjectSettings::get_singleton()->globalize_path(p_path)); - } else { - ERR_PRINTS("C# project could not be created; cannot add file: '" + p_path + "'."); + // The file does not yet exist, let's assume the user just created this script. In such + // cases we need to check whether the solution and csproj were already created or not. + if (!_create_project_solution_if_needed()) { + ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'."); } } #endif @@ -3466,19 +3625,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"); @@ -3488,4 +3644,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"); } |
