diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
| -rw-r--r-- | modules/mono/csharp_script.cpp | 969 |
1 files changed, 448 insertions, 521 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 958d72adb1..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,20 +31,19 @@ #include "csharp_script.h" #include <mono/metadata/threads.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/json.h" -#include "core/os/file_access.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 @@ -125,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); @@ -134,8 +134,13 @@ void CSharpLanguage::init() { } void CSharpLanguage::finish() { - if (finalized) + finalize(); +} + +void CSharpLanguage::finalize() { + if (finalized) { return; + } finalizing = true; @@ -298,6 +303,26 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) 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 @@ -322,7 +347,7 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin 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" @@ -341,14 +366,18 @@ 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; } @@ -359,9 +388,10 @@ bool CSharpLanguage::is_using_templates() { 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); } @@ -390,15 +420,17 @@ bool CSharpLanguage::supports_builtin_mode() const { #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::FLOAT)) { #ifdef REAL_T_IS_DOUBLE @@ -408,36 +440,49 @@ 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::PACKED_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::PACKED_INT32_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::PACKED_INT64_ARRAY)) + } + 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)) + } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { return "float[]"; - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_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)) + } + if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { return "string[]"; - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) + } + 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::PACKED_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::PACKED_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)) + if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) { return "SignalInfo"; + } Variant::Type var_types[] = { Variant::BOOL, @@ -450,20 +495,21 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { 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"; @@ -477,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)); } @@ -516,32 +563,36 @@ String CSharpLanguage::debug_get_error() const { } 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(); @@ -551,15 +602,17 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED // Printing an error here will result in endless recursion, so we must be careful static thread_local bool _recursion_flag_ = false; - if (_recursion_flag_) + 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()); @@ -581,8 +634,9 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() 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_) + if (_recursion_flag_) { return Vector<StackInfo>(); + } _recursion_flag_ = true; SCOPE_EXIT { _recursion_flag_ = false; }; @@ -599,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); @@ -646,8 +701,9 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { 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 } @@ -673,8 +729,9 @@ 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) { @@ -717,14 +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,23 +794,27 @@ bool CSharpLanguage::is_assembly_reloading_needed() { 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. @@ -800,13 +862,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { 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)); } } @@ -817,7 +879,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload.push_back(script); - if (script->get_path().empty()) { + 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(); } @@ -829,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)); } } @@ -840,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 @@ -858,8 +920,9 @@ 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; @@ -894,15 +957,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { 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); - PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj); obj->set_script_instance(placeholder); @@ -929,14 +991,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { 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(); @@ -1092,8 +1153,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // Call OnAfterDeserialization - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) - obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize); + if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { + obj->get_script_instance()->call(string_names.on_after_deserialize); + } } } @@ -1140,46 +1202,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::_load_scripts_metadata() { - scripts_metadata.clear(); +void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { + if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { + return; + } - String scripts_metadata_filename = "scripts_metadata."; + MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); + String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); -#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 + dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( + p_class->get_namespace(), p_class->get_name(), p_class); +} - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); +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); - if (FileAccess::exists(scripts_metadata_path)) { - String old_json; + 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(); - Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - ERR_FAIL_COND(ferr != OK); + 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); - 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_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); - return; - } + if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { + continue; + } - scripts_metadata = old_dict_var.operator Dictionary(); - scripts_metadata_invalidated = false; + 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); - print_verbose("Successfully loaded scripts metadata"); - } else { - if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file."); + int length = mono_array_length(script_types); + + 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); + } } } } @@ -1246,6 +1318,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() { script_binding.inited = false; } +#ifdef GD_MONO_HOT_RELOAD { MutexLock lock(ManagedCallable::instances_mutex); @@ -1255,8 +1328,9 @@ void CSharpLanguage::_on_scripts_domain_unloaded() { managed_callable->delegate_invoke = nullptr; } } +#endif - scripts_metadata_invalidated = true; + dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED @@ -1275,7 +1349,8 @@ void CSharpLanguage::_editor_init_callback() { 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)); + 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 @@ -1324,7 +1399,7 @@ CSharpLanguage::CSharpLanguage() { } CSharpLanguage::~CSharpLanguage() { - finish(); + finalize(); singleton = nullptr; } @@ -1341,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; @@ -1361,16 +1437,16 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b 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; @@ -1380,13 +1456,15 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { 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)) + if (!setup_csharp_script_binding(script_binding, p_object)) { return nullptr; + } return (void *)insert_script_binding(p_object, script_binding); } @@ -1398,14 +1476,15 @@ Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_bindi void CSharpLanguage::free_instance_binding_data(void *p_data) { 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; @@ -1431,10 +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 @@ -1444,10 +1523,11 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); 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. @@ -1455,8 +1535,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { // so the owner must hold the managed side alive again to avoid it from being GCed. MonoObject *target = gchandle.get_target(); - if (!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. MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); @@ -1466,10 +1547,10 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { } 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 @@ -1479,10 +1560,11 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get(); 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_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 GD_MONO_SCOPE_THREAD_ATTACH; @@ -1491,8 +1573,9 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { // the managed instance takes responsibility of deleting the owner when GCed. MonoObject *target = gchandle.get_target(); - if (!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. MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); @@ -1508,14 +1591,15 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - Reference *ref = Object::cast_to<Reference>(p_owner); + RefCounted *rc = Object::cast_to<RefCounted>(p_owner); - instance->base_ref = ref != nullptr; + 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); @@ -1572,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; } @@ -1658,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(); @@ -1679,8 +1765,9 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, const CSharpScript::EventSignal &event_signal = E->value(); MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); - if (!delegate_field_value) + if (!delegate_field_value) { continue; // Empty + } Array serialized_data; MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); @@ -1725,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; } @@ -1739,20 +1827,23 @@ 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; @@ -1806,44 +1897,9 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, 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(!base_ref_counted); CRASH_COND(owner == nullptr); CRASH_COND(unsafe_referenced); // already referenced #endif @@ -1854,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; } @@ -1864,12 +1920,13 @@ bool CSharpInstance::_reference_owner_unsafe() { bool CSharpInstance::_unreference_owner_unsafe() { #ifdef DEBUG_ENABLED - CRASH_COND(!base_ref); + CRASH_COND(!base_ref_counted); CRASH_COND(owner == nullptr); #endif - if (!unsafe_referenced) + if (!unsafe_referenced) { return false; // Already unreferenced + } unsafe_referenced = false; @@ -1880,7 +1937,7 @@ 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() { @@ -1902,7 +1959,7 @@ 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 = nullptr; @@ -1912,8 +1969,9 @@ MonoObject *CSharpInstance::_internal_new_managed() { // Tie managed to unmanaged 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); @@ -1924,10 +1982,11 @@ MonoObject *CSharpInstance::_internal_new_managed() { } 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(base_ref_counted); CRASH_COND(gchandle.is_released()); #endif CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); @@ -1935,10 +1994,13 @@ void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { 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(!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()) { @@ -1973,35 +2035,33 @@ void CSharpInstance::connect_event_signals() { StringName signal_name = event_signal.field->get_name(); // TODO: Use pooling for ManagedCallable instances. - auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); - owner->connect(signal_name, Callable(event_signal_callable)); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); } } void CSharpInstance::disconnect_event_signals() { - for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) { - const CSharpScript::EventSignal &event_signal = E->value(); - - StringName signal_name = event_signal.field->get_name(); - - // TODO: It would be great if we could store this EventSignalCallable on the stack. - // The problem is that Callable memdeletes it when it's destructed... - auto event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); - - owner->disconnect(signal_name, Callable(event_signal_callable)); + 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(!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. @@ -2017,13 +2077,13 @@ void CSharpInstance::refcount_incremented() { bool CSharpInstance::refcount_decremented() { #ifdef DEBUG_ENABLED - CRASH_COND(!base_ref); + 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 GD_MONO_SCOPE_THREAD_ATTACH; @@ -2044,46 +2104,10 @@ bool CSharpInstance::refcount_decremented() { return ref_dying; } -Vector<ScriptNetData> CSharpInstance::get_rpc_methods() const { +const Vector<MultiplayerAPI::RPCConfig> CSharpInstance::get_rpc_methods() const { return script->get_rpc_methods(); } -uint16_t CSharpInstance::get_rpc_method_id(const StringName &p_method) const { - return script->get_rpc_method_id(p_method); -} - -StringName CSharpInstance::get_rpc_method(const uint16_t p_rpc_method_id) const { - return script->get_rpc_method(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - return script->get_rpc_mode_by_id(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { - return script->get_rpc_mode(p_method); -} - -Vector<ScriptNetData> CSharpInstance::get_rset_properties() const { - return script->get_rset_properties(); -} - -uint16_t CSharpInstance::get_rset_property_id(const StringName &p_variable) const { - return script->get_rset_property_id(p_variable); -} - -StringName CSharpInstance::get_rset_property(const uint16_t p_rset_member_id) const { - return script->get_rset_property(p_rset_member_id); -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - return script->get_rset_mode_by_id(p_rset_member_id); -} - -MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { - return script->get_rset_mode(p_variable); -} - void CSharpInstance::notification(int p_notification) { GD_MONO_SCOPE_THREAD_ATTACH; @@ -2094,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; } @@ -2130,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); @@ -2154,8 +2178,9 @@ String CSharpInstance::to_string(bool *r_valid) { MonoObject *mono_object = get_mono_object(); if (mono_object == nullptr) { - if (r_valid) + if (r_valid) { *r_valid = false; + } return String(); } @@ -2164,14 +2189,16 @@ String CSharpInstance::to_string(bool *r_valid) { if (exc) { GDMonoUtils::set_pending_exception(exc); - if (r_valid) + if (r_valid) { *r_valid = false; + } return String(); } if (result == nullptr) { - if (r_valid) + if (r_valid) { *r_valid = false; + } return String(); } @@ -2195,6 +2222,9 @@ CSharpInstance::~CSharpInstance() { destructing_script_instance = true; + // 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. @@ -2219,21 +2249,21 @@ CSharpInstance::~CSharpInstance() { } // 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 == nullptr); @@ -2252,7 +2282,7 @@ 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 } @@ -2339,14 +2369,16 @@ void CSharpScript::_update_member_info_no_exports() { } #endif -bool CSharpScript::_update_exports() { +bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) { #ifdef TOOLS_ENABLED bool is_editor = Engine::get_singleton()->is_editor_hint(); - if (is_editor) + if (is_editor) { placeholder_fallback_enabled = true; // until proven otherwise + } #endif - if (!valid) + if (!valid) { return false; + } bool changed = false; @@ -2478,7 +2510,7 @@ bool CSharpScript::_update_exports() { #ifdef TOOLS_ENABLED if (is_editor) { // Need to check this here, before disposal - bool base_ref = Object::cast_to<Reference>(tmp_native) != nullptr; + bool base_ref_counted = Object::cast_to<RefCounted>(tmp_native) != nullptr; // Dispose the temporary managed instance @@ -2493,7 +2525,7 @@ bool CSharpScript::_update_exports() { GDMonoUtils::free_gchandle(tmp_pinned_gchandle); tmp_object = nullptr; - if (tmp_native && !base_ref) { + 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."); @@ -2509,14 +2541,18 @@ bool CSharpScript::_update_exports() { if (is_editor) { placeholder_fallback_enabled = false; - if (placeholders.size()) { + if ((changed || p_instance_to_update) && 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); + 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); } } } @@ -2543,8 +2579,9 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati for (int i = delegates.size() - 1; i >= 0; --i) { GDMonoClass *delegate = delegates[i]; - if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) + 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())); @@ -2563,7 +2600,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event); if (event_attrs) { if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) { - const char *event_name = mono_event_get_name(raw_event); + String event_name = String::utf8(mono_event_get_name(raw_event)); found_event_signals.push_back(StringName(event_name)); } @@ -2577,11 +2614,13 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati GDMonoClass *field_class = field->get_type().type_class; - if (!mono_class_is_delegate(field_class->get_mono_ptr())) + if (!mono_class_is_delegate(field_class->get_mono_ptr())) { continue; + } - if (!found_event_signals.find(field->get_name())) + 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())); @@ -2640,14 +2679,16 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->is_static()) { #ifdef TOOLS_ENABLED - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) + 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; @@ -2665,15 +2706,17 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { #ifdef TOOLS_ENABLED - if (exported) - ERR_PRINT("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + if (exported) { + ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + } #endif return false; } if (!property->has_setter()) { #ifdef TOOLS_ENABLED - if (exported) - ERR_PRINT("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + if (exported) { + ERR_PRINT("Cannot export a property without a setter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); + } #endif return false; } @@ -2688,7 +2731,9 @@ 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; @@ -2759,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; @@ -2800,8 +2845,9 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage 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); @@ -2828,15 +2874,6 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage } #endif -void CSharpScript::_clear() { - tool = false; - valid = false; - - base = nullptr; - native = nullptr; - script_class = nullptr; -} - Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (unlikely(GDMono::get_singleton() == nullptr)) { // Probably not the best error but eh. @@ -2869,11 +2906,7 @@ 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 { @@ -2927,12 +2960,24 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon 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) { @@ -2940,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(); } @@ -2956,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(); } @@ -2966,20 +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 { +bool CSharpScript::can_instantiate() const { #ifdef TOOLS_ENABLED bool extra_cond = tool || ScriptServer::is_scripting_enabled(); #else @@ -3003,13 +3073,14 @@ 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, Callable::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 */ @@ -3020,15 +3091,15 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg 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(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 @@ -3054,7 +3125,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg } CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this))); - instance->base_ref = p_isref; + instance->base_ref_counted = p_is_ref_counted; instance->owner = p_owner; instance->owner->set_script_instance(instance); @@ -3069,7 +3140,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg 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(nullptr); r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; @@ -3079,8 +3150,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // Tie managed to unmanaged 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) + } { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); @@ -3110,10 +3182,10 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal 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); } @@ -3144,24 +3216,24 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { if (EngineDebugger::is_active()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + String(native_name) + - "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); } ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + - "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); } } GD_MONO_SCOPE_THREAD_ATTACH; Callable::CallError unchecked_error; - return _create_instance(nullptr, 0, p_this, Object::cast_to<Reference>(p_this) != nullptr, 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 nullptr; @@ -3174,7 +3246,7 @@ bool CSharpScript::instance_has(const Object *p_this) const { } bool CSharpScript::has_source_code() const { - return !source.empty(); + return !source.is_empty(); } String CSharpScript::get_source_code() const { @@ -3182,8 +3254,9 @@ String CSharpScript::get_source_code() const { } 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; @@ -3191,8 +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; @@ -3204,8 +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; @@ -3213,8 +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; @@ -3243,152 +3319,34 @@ 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 && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) { - script_class = klass; - } - } else { - // Missing script metadata. Fallback to legacy method - script_class = project_assembly->get_object_derived_class(name); + const DotNetScriptLookupInfo *lookup_info = + CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); + + 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; } + } - valid = script_class != nullptr; + valid = script_class != nullptr; - if (script_class) { + 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(); - } + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); - - CRASH_COND(native == nullptr); + native = GDMonoUtils::get_class_native_base(script_class); - GDMonoClass *base_class = script_class->get_parent_class(); + CRASH_COND(native == nullptr); - if (base_class != native) - base = base_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(); - } - } -#endif - - script_class->fetch_methods_with_godot_api_checks(native); - - // 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(); - } + update_script_class_info(this); - load_script_signals(script_class, native); - _update_exports(); - } - - rpc_functions.clear(); - rpc_variables.clear(); - - GDMonoClass *top = script_class; - while (top && top != native) { - { - Vector<GDMonoMethod *> methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); i++) { - if (!methods[i]->is_static()) { - MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(methods[i]); - if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { - ScriptNetData nd; - nd.name = methods[i]->get_name(); - nd.mode = mode; - if (-1 == rpc_functions.find(nd)) { - rpc_functions.push_back(nd); - } - } - } - } - } - - { - Vector<GDMonoField *> fields = top->get_all_fields(); - for (int i = 0; i < fields.size(); i++) { - if (!fields[i]->is_static()) { - MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(fields[i]); - if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { - ScriptNetData nd; - nd.name = fields[i]->get_name(); - nd.mode = mode; - if (-1 == rpc_variables.find(nd)) { - rpc_variables.push_back(nd); - } - } - } - } - } - - { - Vector<GDMonoProperty *> properties = top->get_all_properties(); - for (int i = 0; i < properties.size(); i++) { - if (!properties[i]->is_static()) { - MultiplayerAPI::RPCMode mode = _member_get_rpc_mode(properties[i]); - if (MultiplayerAPI::RPC_MODE_DISABLED != mode) { - ScriptNetData nd; - nd.name = properties[i]->get_name(); - nd.mode = mode; - if (-1 == rpc_variables.find(nd)) { - rpc_variables.push_back(nd); - } - } - } - } - } - - top = top->get_parent_class(); - } - - // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<SortNetData>(); - rpc_variables.sort_custom<SortNetData>(); - - return OK; + _update_exports(); } - return ERR_FILE_MISSING_DEPENDENCIES; + return OK; } ScriptLanguage *CSharpScript::get_language() const { @@ -3432,8 +3390,9 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { const SignalParameter ¶m = params[i]; PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) + if (param.type == Variant::NIL && param.nil_is_variant) { arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } mi.arguments.push_back(arg_info); } @@ -3451,8 +3410,9 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { const SignalParameter ¶m = params[i]; PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) + if (param.type == Variant::NIL && param.nil_is_variant) { arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } mi.arguments.push_back(arg_info); } @@ -3495,84 +3455,40 @@ int CSharpScript::get_member_line(const StringName &p_member) const { } MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const { - if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) + if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) { return MultiplayerAPI::RPC_MODE_REMOTE; - if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) + } + if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) { return MultiplayerAPI::RPC_MODE_MASTER; - if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) + } + if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) { return MultiplayerAPI::RPC_MODE_PUPPET; - if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) + } + if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) { return MultiplayerAPI::RPC_MODE_REMOTESYNC; - if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) + } + if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) { return MultiplayerAPI::RPC_MODE_MASTERSYNC; - if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) + } + if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) { return MultiplayerAPI::RPC_MODE_PUPPETSYNC; + } return MultiplayerAPI::RPC_MODE_DISABLED; } -Vector<ScriptNetData> CSharpScript::get_rpc_methods() const { +const Vector<MultiplayerAPI::RPCConfig> CSharpScript::get_rpc_methods() const { return rpc_functions; } -uint16_t CSharpScript::get_rpc_method_id(const StringName &p_method) const { - for (int i = 0; i < rpc_functions.size(); i++) { - if (rpc_functions[i].name == p_method) { - return i; - } - } - return UINT16_MAX; -} - -StringName CSharpScript::get_rpc_method(const uint16_t p_rpc_method_id) const { - ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), StringName()); - return rpc_functions[p_rpc_method_id].name; -} - -MultiplayerAPI::RPCMode CSharpScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - ERR_FAIL_COND_V(p_rpc_method_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED); - return rpc_functions[p_rpc_method_id].mode; -} - -MultiplayerAPI::RPCMode CSharpScript::get_rpc_mode(const StringName &p_method) const { - return get_rpc_mode_by_id(get_rpc_method_id(p_method)); -} - -Vector<ScriptNetData> CSharpScript::get_rset_properties() const { - return rpc_variables; -} - -uint16_t CSharpScript::get_rset_property_id(const StringName &p_variable) const { - for (int i = 0; i < rpc_variables.size(); i++) { - if (rpc_variables[i].name == p_variable) { - return i; - } - } - return UINT16_MAX; -} - -StringName CSharpScript::get_rset_property(const uint16_t p_rset_member_id) const { - ERR_FAIL_COND_V(p_rset_member_id >= rpc_variables.size(), StringName()); - return rpc_variables[p_rset_member_id].name; -} - -MultiplayerAPI::RPCMode CSharpScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - ERR_FAIL_COND_V(p_rset_member_id >= rpc_functions.size(), MultiplayerAPI::RPC_MODE_DISABLED); - return rpc_functions[p_rset_member_id].mode; -} - -MultiplayerAPI::RPCMode CSharpScript::get_rset_mode(const StringName &p_variable) const { - return get_rset_mode_by_id(get_rset_property_id(p_variable)); -} - Error CSharpScript::load_source_code(const String &p_path) { Error ferr = read_all_file_utf8(p_path, source); ERR_FAIL_COND_V_MSG(ferr != OK, ferr, 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; @@ -3581,14 +3497,27 @@ Error CSharpScript::load_source_code(const String &p_path) { return OK; } -StringName CSharpScript::get_script_name() const { - return name; +void CSharpScript::_update_name() { + String path = get_path(); + + if (!path.is_empty()) { + name = get_path().get_file().get_basename(); + } +} + +void CSharpScript::_clear() { + tool = false; + valid = false; + + base = nullptr; + native = nullptr; + script_class = nullptr; } CSharpScript::CSharpScript() { _clear(); - _resource_path_changed(); + _update_name(); #ifdef DEBUG_ENABLED { @@ -3617,9 +3546,10 @@ void CSharpScript::get_members(Set<StringName> *p_members) { /*************** RESOURCE ***************/ -RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, bool p_no_cache) { - if (r_error) +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? @@ -3636,8 +3566,9 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p script->reload(); - if (r_error) + if (r_error) { *r_error = OK; + } return scriptres; } @@ -3662,13 +3593,9 @@ 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 { + // 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 + "'."); } } |
