diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 133 |
1 files changed, 96 insertions, 37 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index b41f2155f8..ef24dc35ca 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -121,16 +121,16 @@ void CSharpLanguage::init() { GLOBAL_DEF(PropertyInfo(Variant::INT, "dotnet/project/assembly_reload_attempts", PROPERTY_HINT_RANGE, "1,16,1,or_greater"), 3); #endif - gdmono = memnew(GDMono); - gdmono->initialize(); - #ifdef TOOLS_ENABLED - if (gdmono->is_runtime_initialized()) { - gdmono->initialize_load_assemblies(); - } - EditorNode::add_init_callback(&_editor_init_callback); #endif + + gdmono = memnew(GDMono); + + // Initialize only if the project uses C#. + if (gdmono->should_initialize()) { + gdmono->initialize(); + } } void CSharpLanguage::finish() { @@ -142,6 +142,10 @@ void CSharpLanguage::finalize() { return; } + if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown(); + } + finalizing = true; // Make sure all script binding gchandles are released before finalizing GDMono @@ -328,6 +332,11 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("/* */"); // delimited comment } +void CSharpLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const { + p_delimiters->push_back("///"); // single-line doc comment + p_delimiters->push_back("/** */"); // delimited doc comment +} + void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal @@ -362,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++) { @@ -392,10 +401,6 @@ Script *CSharpLanguage::create_script() const { return memnew(CSharpScript); } -bool CSharpLanguage::has_named_classes() const { - return false; -} - bool CSharpLanguage::supports_builtin_mode() const { return false; } @@ -554,13 +559,13 @@ 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()); - if (!scr.is_valid() || !scr->valid || !scr->global_class) { - // Invalid script or the script is not a global class. - return String(); - } + // 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 - String name = scr->class_name; - if (unlikely(name.is_empty())) { + if (!scr.is_valid() || !scr->valid) { + // Invalid script. return String(); } @@ -587,7 +592,8 @@ String CSharpLanguage::get_global_class_name(const String &p_path, String *r_bas *r_base_type = scr->get_instance_base_type(); } } - return name; + + return scr->global_class ? scr->class_name : String(); } String CSharpLanguage::debug_get_error() const { @@ -714,11 +720,22 @@ 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) { CRASH_COND(!Engine::get_singleton()->is_editor_hint()); + bool has_csharp_script = false; + for (int i = 0; i < p_scripts.size(); ++i) { + Ref<CSharpScript> cs_script = p_scripts[i]; + if (cs_script.is_valid()) { + has_csharp_script = true; + break; + } + } + + if (!has_csharp_script) { + return; + } + #ifdef TOOLS_ENABLED get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer"); #endif @@ -730,6 +747,12 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #endif } +void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { + Array scripts; + scripts.push_back(p_script); + reload_scripts(scripts, p_soft_reload); +} + #ifdef GD_MONO_HOT_RELOAD bool CSharpLanguage::is_assembly_reloading_needed() { ERR_FAIL_NULL_V(gdmono, false); @@ -1068,7 +1091,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 @@ -1983,24 +2006,31 @@ const Variant CSharpInstance::get_rpc_config() const { void CSharpInstance::notification(int p_notification, bool p_reversed) { if (p_notification == Object::NOTIFICATION_PREDELETE) { - // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). - // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed + if (base_ref_counted) { + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted 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# 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; + } + } else if (p_notification == Object::NOTIFICATION_PREDELETE_CLEANUP) { + // When NOTIFICATION_PREDELETE_CLEANUP is sent, we also take the chance to call Dispose(). + // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE_CLEANUP is guaranteed // to be sent at least once, which happens right before the call to the destructor. predelete_notified = true; 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# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but - // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted wouldn't have reached 0 otherwise, since the managed side + // references it and Dispose() needs to be called to release it. return; } - _call_notification(p_notification, p_reversed); - + // NOTIFICATION_PREDELETE_CLEANUP is not sent to scripts. + // After calling Dispose() the C# instance can no longer be used, + // so it should be the last thing we do. GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( gchandle.get_intptr(), /* okIfNull */ false); @@ -2233,6 +2263,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 @@ -2286,7 +2327,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(); @@ -2300,6 +2341,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { bool tool = false; bool global_class = false; + bool abstract_class = false; // TODO: Use GDExtension godot_dictionary Array methods_array; @@ -2313,12 +2355,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { String icon_path; Ref<CSharpScript> base_script; GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( - p_script.ptr(), &class_name, &tool, &global_class, &icon_path, + p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path, &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->rpc_config.clear(); @@ -2339,6 +2382,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++) { @@ -2353,6 +2398,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { mi.arguments.push_back(arg_info); } + mi.flags = (uint32_t)method_info_dict["flags"]; + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } @@ -2406,7 +2453,7 @@ bool CSharpScript::can_instantiate() const { 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)."); } - return valid && extra_cond; + return valid && !abstract_class && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -2602,6 +2649,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { return MethodInfo(); } +Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (valid) { + Variant ret; + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret); + if (ok) { + return ret; + } + } + + return Script::callp(p_method, p_args, p_argcount, r_error); +} + Error CSharpScript::reload(bool p_keep_state) { if (!reload_invalidated) { return OK; @@ -2624,7 +2683,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(); |