diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 432 |
1 files changed, 189 insertions, 243 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 8e1587997b..ff2ca9f0ce 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -307,7 +307,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const { } } -bool CSharpLanguage::is_control_flow_keyword(String p_keyword) const { +bool CSharpLanguage::is_control_flow_keyword(const String &p_keyword) const { return p_keyword == "break" || p_keyword == "case" || p_keyword == "catch" || @@ -371,7 +371,7 @@ Ref<Script> CSharpLanguage::make_template(const String &p_template, const String return scr; } -Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) { +Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(const StringName &p_object) { Vector<ScriptLanguage::ScriptTemplate> templates; #ifdef TOOLS_ENABLED for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) { @@ -405,133 +405,17 @@ bool CSharpLanguage::supports_builtin_mode() const { return false; } -#ifdef TOOLS_ENABLED -struct VariantCsName { - Variant::Type variant_type; - const String cs_type; -}; - -static String variant_type_to_managed_name(const String &p_var_type_name) { - if (p_var_type_name.is_empty()) { - return "Variant"; - } - - if (ClassDB::class_exists(p_var_type_name)) { - return pascal_to_pascal_case(p_var_type_name); - } - - if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) { - return "GodotObject"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::INT)) { - return "long"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) { - return "double"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::STRING)) { - return "string"; // I prefer this one >:[ - } - - if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) { - return "Collections.Dictionary"; - } - - if (p_var_type_name.begins_with(Variant::get_type_name(Variant::ARRAY) + "[")) { - String element_type = p_var_type_name.trim_prefix(Variant::get_type_name(Variant::ARRAY) + "[").trim_suffix("]"); - return "Collections.Array<" + variant_type_to_managed_name(element_type) + ">"; - } - - 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)) { - return "byte[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) { - return "int[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) { - return "long[]"; - } - 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)) { - return "double[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { - return "string[]"; - } - if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { - return "Vector2[]"; - } - 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)) { - return "Color[]"; - } - - if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) { - return "Signal"; - } - - const VariantCsName var_types[] = { - { Variant::BOOL, "bool" }, - { Variant::INT, "long" }, - { Variant::VECTOR2, "Vector2" }, - { Variant::VECTOR2I, "Vector2I" }, - { Variant::RECT2, "Rect2" }, - { Variant::RECT2I, "Rect2I" }, - { Variant::VECTOR3, "Vector3" }, - { Variant::VECTOR3I, "Vector3I" }, - { Variant::TRANSFORM2D, "Transform2D" }, - { Variant::VECTOR4, "Vector4" }, - { Variant::VECTOR4I, "Vector4I" }, - { Variant::PLANE, "Plane" }, - { Variant::QUATERNION, "Quaternion" }, - { Variant::AABB, "Aabb" }, - { Variant::BASIS, "Basis" }, - { Variant::TRANSFORM3D, "Transform3D" }, - { Variant::PROJECTION, "Projection" }, - { Variant::COLOR, "Color" }, - { Variant::STRING_NAME, "StringName" }, - { Variant::NODE_PATH, "NodePath" }, - { Variant::RID, "Rid" }, - { Variant::CALLABLE, "Callable" }, - }; - - for (unsigned int i = 0; i < sizeof(var_types) / sizeof(VariantCsName); i++) { - if (p_var_type_name == Variant::get_type_name(var_types[i].variant_type)) { - return var_types[i].cs_type; - } - } - - return "Variant"; +ScriptLanguage::ScriptNameCasing CSharpLanguage::preferred_file_name_casing() const { + return SCRIPT_NAME_CASING_PASCAL_CASE; } +#ifdef TOOLS_ENABLED 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 - String s = "private void " + p_name + "("; - for (int i = 0; i < p_args.size(); i++) { - const String &arg = p_args[i]; - - if (i > 0) { - s += ", "; - } - - s += variant_type_to_managed_name(arg.get_slice(":", 1)) + " " + escape_csharp_keyword(arg.get_slice(":", 0)); - } - s += ")\n{\n // Replace with function body.\n}\n"; - - return s; + // The make_function() API does not work for C# scripts. + // It will always append the generated function at the very end of the script. In C#, it will break compilation by + // appending code after the final closing bracket (either the class' or the namespace's). + // To prevent issues, we have can_make_function() returning false, and make_function() is never implemented. + return String(); } #else String CSharpLanguage::make_function(const String &, const String &, const PackedStringArray &) const { @@ -558,42 +442,9 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const { } String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { - Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type()); - // Always assign r_base_type and r_icon_path, even if the script - // is not a global one. In the case that it is not a global script, - // return an empty string AFTER assigning the return parameters. - // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp - - if (!scr.is_valid() || !scr->valid) { - // Invalid script. - return String(); - } - - if (r_icon_path) { - if (scr->icon_path.is_empty() || scr->icon_path.is_absolute_path()) { - *r_icon_path = scr->icon_path.simplify_path(); - } else if (scr->icon_path.is_relative_path()) { - *r_icon_path = p_path.get_base_dir().path_join(scr->icon_path).simplify_path(); - } - } - if (r_base_type) { - bool found_global_base_script = false; - const CSharpScript *top = scr->base_script.ptr(); - while (top != nullptr) { - if (top->global_class) { - *r_base_type = top->class_name; - found_global_base_script = true; - break; - } - - top = top->base_script.ptr(); - } - if (!found_global_base_script) { - *r_base_type = scr->get_instance_base_type(); - } - } - - return scr->global_class ? scr->class_name : String(); + String class_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetGlobalClassName(&p_path, r_base_type, r_icon_path, &class_name); + return class_name; } String CSharpLanguage::debug_get_error() const { @@ -720,9 +571,15 @@ void CSharpLanguage::reload_all_scripts() { #endif } -void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { - (void)p_script; // UNUSED +void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + if (is_assembly_reloading_needed()) { + reload_assemblies(p_soft_reload); + } +#endif +} +void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { CRASH_COND(!Engine::get_singleton()->is_editor_hint()); #ifdef TOOLS_ENABLED @@ -925,7 +782,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { obj->set_script(Ref<RefCounted>()); // Remove script and existing script instances (placeholder are not removed before domain reload) } - scr->was_tool_before_reload = scr->tool; + scr->was_tool_before_reload = scr->type_info.is_tool; scr->_clear(); } @@ -985,7 +842,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->exports_invalidated = true; #endif - if (!scr->get_path().is_empty()) { + if (!scr->get_path().is_empty() && !scr->get_path().begins_with("csharp://")) { scr->reload(p_soft_reload); if (!scr->valid) { @@ -1074,7 +931,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // The script instance could not be instantiated or wasn't in the list of placeholders to replace. obj->set_script(scr); -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED // If we reached here, the instantiated script must be a placeholder. CRASH_COND(!obj->get_script_instance()->is_placeholder()); #endif @@ -1084,6 +941,31 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload_state.push_back(scr); } + // Deserialize managed callables. + // This is done before reloading script's internal state, so potential callables invoked in properties work. + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { + ManagedCallable *managed_callable = elem.key; + const Array &serialized_data = elem.value; + + GCHandleIntPtr delegate = { nullptr }; + + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( + &serialized_data, &delegate); + + if (success) { + ERR_CONTINUE(delegate.value == nullptr); + managed_callable->delegate_handle = delegate; + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize delegate\n"); + } + } + + ManagedCallable::instances_pending_reload.clear(); + } + for (Ref<CSharpScript> &scr : to_reload_state) { for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); @@ -1106,7 +988,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { properties[G.first] = G.second; } - // Restore serialized state and call OnAfterDeserialization + // Restore serialized state and call OnAfterDeserialize. GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState( csi->get_gchandle_intptr(), &properties, &state_backup.event_signals); } @@ -1116,30 +998,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.clear(); } - // Deserialize managed callables - { - MutexLock lock(ManagedCallable::instances_mutex); - - for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { - ManagedCallable *managed_callable = elem.key; - const Array &serialized_data = elem.value; - - GCHandleIntPtr delegate = { nullptr }; - - bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( - &serialized_data, &delegate); - - if (success) { - ERR_CONTINUE(delegate.value == nullptr); - managed_callable->delegate_handle = 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()) { @@ -1442,7 +1300,11 @@ GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_tok } void *CSharpLanguage::get_instance_binding(Object *p_object) { - void *binding = p_object->get_instance_binding(get_singleton(), &_instance_binding_callbacks); + return p_object->get_instance_binding(get_singleton(), &_instance_binding_callbacks); +} + +void *CSharpLanguage::get_instance_binding_with_setup(Object *p_object) { + void *binding = get_instance_binding(p_object); // Initially this was in `_instance_binding_create_callback`. However, after the new instance // binding re-write it was resulting in a deadlock in `_instance_binding_reference`, as @@ -1467,11 +1329,7 @@ void *CSharpLanguage::get_existing_instance_binding(Object *p_object) { #ifdef DEBUG_ENABLED CRASH_COND(p_object->has_instance_binding(p_object)); #endif - return p_object->get_instance_binding(get_singleton(), &_instance_binding_callbacks); -} - -void CSharpLanguage::set_instance_binding(Object *p_object, void *p_binding) { - p_object->set_instance_binding(get_singleton(), p_binding, &_instance_binding_callbacks); + return get_instance_binding(p_object); } bool CSharpLanguage::has_instance_binding(Object *p_object) { @@ -1498,13 +1356,6 @@ void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_i // Another reason for doing this is that this instance could outlive CSharpLanguage, which would // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 - CSharpScriptBinding script_binding; - - script_binding.inited = true; - script_binding.type_name = *p_native_name; - script_binding.gchandle = gchandle; - script_binding.owner = p_unmanaged; - if (p_ref_counted) { // Unsafe refcount increment. The managed instance also counts as a reference. // This way if the unmanaged world has no references to our owner @@ -1520,14 +1371,13 @@ void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_i // The object was just created, no script instance binding should have been attached CRASH_COND(CSharpLanguage::has_instance_binding(p_unmanaged)); - void *data; - { - MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); - data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(p_unmanaged, script_binding); - } + void *binding = CSharpLanguage::get_singleton()->get_instance_binding(p_unmanaged); - // Should be thread safe because the object was just created and nothing else should be referencing it - CSharpLanguage::set_instance_binding(p_unmanaged, data); + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)binding)->value(); + script_binding.inited = true; + script_binding.type_name = *p_native_name; + script_binding.gchandle = gchandle; + script_binding.owner = p_unmanaged; } void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) { @@ -1769,6 +1619,34 @@ bool CSharpInstance::has_method(const StringName &p_method) const { gchandle.get_intptr(), &p_method); } +int CSharpInstance::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (!script->is_valid() || !script->valid) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + const CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (const CSharpScript::CSharpMethodInfo &E : top->methods) { + if (E.name == p_method) { + if (r_is_valid) { + *r_is_valid = true; + } + return E.method_info.arguments.size(); + } + } + + top = top->base_script.ptr(); + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { ERR_FAIL_COND_V(!script.is_valid(), Variant()); @@ -1827,6 +1705,7 @@ bool CSharpInstance::_internal_new_managed() { ERR_FAIL_NULL_V(owner, false); ERR_FAIL_COND_V(script.is_null(), false); + ERR_FAIL_COND_V(!script->can_instantiate(), false); bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance( script.ptr(), owner, nullptr, 0); @@ -2097,7 +1976,7 @@ CSharpInstance::~CSharpInstance() { bool die = _unreference_owner_unsafe(); CRASH_COND(die); // `owner_keep_alive` holds a reference, so it can't die - void *data = CSharpLanguage::get_instance_binding(owner); + void *data = CSharpLanguage::get_instance_binding_with_setup(owner); CRASH_COND(data == nullptr); CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); CRASH_COND(!script_binding.inited); @@ -2149,7 +2028,7 @@ void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript #ifdef TOOLS_ENABLED p_script->exported_members_cache.push_back(PropertyInfo( - Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE, + Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE, p_script->get_path(), PROPERTY_USAGE_CATEGORY)); #endif @@ -2246,6 +2125,17 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda } else { p_instance_to_update->update(propnames, values); } + } else if (placeholders.size()) { + uint64_t script_modified_time = FileAccess::get_modified_time(get_path()); + uint64_t last_valid_build_time = GDMono::get_singleton()->get_project_assembly_modified_time(); + if (script_modified_time > last_valid_build_time) { + for (PlaceHolderScriptInstance *instance : placeholders) { + Object *owner = instance->get_owner(); + if (owner->get_script_instance() == instance) { + owner->notify_property_list_changed(); + } + } + } } } #endif @@ -2299,7 +2189,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { p_script->_update_exports(); -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); @@ -2311,9 +2201,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { // Extract information about the script using the mono class. void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { - bool tool = false; - bool global_class = false; - bool abstract_class = false; + TypeInfo type_info; // TODO: Use GDExtension godot_dictionary Array methods_array; @@ -2323,18 +2211,12 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { Dictionary signals_dict; signals_dict.~Dictionary(); - String class_name; - String icon_path; Ref<CSharpScript> base_script; GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( - p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path, + p_script.ptr(), &type_info, &methods_array, &rpc_functions_dict, &signals_dict, &base_script); - p_script->class_name = class_name; - p_script->tool = tool; - p_script->global_class = global_class; - p_script->abstract_class = abstract_class; - p_script->icon_path = icon_path; + p_script->type_info = type_info; p_script->rpc_config.clear(); p_script->rpc_config = rpc_functions_dict; @@ -2354,6 +2236,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { MethodInfo mi; mi.name = name; + mi.return_val = PropertyInfo::from_dict(method_info_dict["return_val"]); + Array params = method_info_dict["params"]; for (int j = 0; j < params.size(); j++) { @@ -2411,7 +2295,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { bool CSharpScript::can_instantiate() const { #ifdef TOOLS_ENABLED - bool extra_cond = tool || ScriptServer::is_scripting_enabled(); + bool extra_cond = type_info.is_tool || ScriptServer::is_scripting_enabled(); #else bool extra_cond = true; #endif @@ -2420,10 +2304,10 @@ bool CSharpScript::can_instantiate() 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 && !valid) { - ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive)."); + ERR_FAIL_V_MSG(false, "Cannot instantiate C# script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive)."); } - return valid && !abstract_class && extra_cond; + return valid && type_info.can_instantiate() && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -2433,6 +2317,8 @@ StringName CSharpScript::get_instance_base_type() const { } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { + ERR_FAIL_COND_V_MSG(!type_info.can_instantiate(), nullptr, "Cannot instantiate C# script. Script: '" + get_path() + "'."); + /* STEP 1, CREATE */ Ref<RefCounted> ref; @@ -2605,18 +2491,48 @@ bool CSharpScript::has_method(const StringName &p_method) const { return false; } +int CSharpScript::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (!valid) { + if (r_is_valid) { + *r_is_valid = false; + } + return 0; + } + + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + if (r_is_valid) { + *r_is_valid = true; + } + return E.method_info.arguments.size(); + } + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { if (!valid) { return MethodInfo(); } + MethodInfo mi; for (const CSharpMethodInfo &E : methods) { if (E.name == p_method) { - return E.method_info; + if (mi.name == p_method) { + // We already found a method with the same name before so + // that means this method has overloads, the best we can do + // is return an empty MethodInfo. + return MethodInfo(); + } + mi = E.method_info; } } - return MethodInfo(); + return mi; } Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -2653,7 +2569,7 @@ Error CSharpScript::reload(bool p_keep_state) { _update_exports(); -#if TOOLS_ENABLED +#ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); @@ -2747,11 +2663,11 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { } Ref<Script> CSharpScript::get_base_script() const { - return base_script.is_valid() && !base_script->get_path().is_empty() ? base_script : nullptr; + return base_script; } StringName CSharpScript::get_global_name() const { - return global_class ? StringName(class_name) : StringName(); + return type_info.is_global_class ? StringName(type_info.class_name) : StringName(); } void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const { @@ -2808,7 +2724,7 @@ Error CSharpScript::load_source_code(const String &p_path) { } void CSharpScript::_clear() { - tool = false; + type_info = TypeInfo(); valid = false; reload_invalidated = true; } @@ -2819,15 +2735,17 @@ CSharpScript::CSharpScript() { #ifdef DEBUG_ENABLED { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); - CSharpLanguage::get_singleton()->script_list.add(&this->script_list); + CSharpLanguage::get_singleton()->script_list.add(&script_list); } #endif } CSharpScript::~CSharpScript() { #ifdef DEBUG_ENABLED - MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); - CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); + { + MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); + CSharpLanguage::get_singleton()->script_list.remove(&script_list); + } #endif if (GDMonoCache::godot_api_cache_updated) { @@ -2854,20 +2772,48 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const // TODO ignore anything inside bin/ and obj/ in tools builds? + String real_path = p_path; + if (p_path.begins_with("csharp://")) { + // This is a virtual path used by generic types, extract the real path. + real_path = "res://" + p_path.trim_prefix("csharp://"); + real_path = real_path.substr(0, real_path.rfind(":")); + } + Ref<CSharpScript> scr; if (GDMonoCache::godot_api_cache_updated) { GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); + ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'."); } else { scr = Ref<CSharpScript>(memnew(CSharpScript)); } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) - Error err = scr->load_source_code(p_path); - ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + p_path + "'."); -#endif - - scr->set_path(p_original_path); + Error err = scr->load_source_code(real_path); + ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot load C# script file '" + real_path + "'."); +#endif + + // Only one instance of a C# script is allowed to exist. + ERR_FAIL_COND_V_MSG(!scr->get_path().is_empty() && scr->get_path() != p_original_path, Ref<Resource>(), + "The C# script path is different from the path it was registered in the C# dictionary."); + + Ref<Resource> existing = ResourceCache::get_ref(p_path); + switch (p_cache_mode) { + case ResourceFormatLoader::CACHE_MODE_IGNORE: + case ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP: + break; + case ResourceFormatLoader::CACHE_MODE_REUSE: + if (existing.is_null()) { + scr->set_path(p_original_path); + } else { + scr = existing; + } + break; + case ResourceFormatLoader::CACHE_MODE_REPLACE: + case ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP: + scr->set_path(p_original_path, true); + break; + } scr->reload(); |