diff options
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 215 |
1 files changed, 130 insertions, 85 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index e06440230b..3cc32bff10 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -30,7 +30,23 @@ #include "csharp_script.h" -#include <stdint.h> +#include "godotsharp_dirs.h" +#include "managed_callable.h" +#include "mono_gd/gd_mono_cache.h" +#include "signal_awaiter_utils.h" +#include "utils/macros.h" +#include "utils/naming_utils.h" +#include "utils/path_utils.h" +#include "utils/string_utils.h" + +#ifdef DEBUG_METHODS_ENABLED +#include "class_db_api_json.h" +#endif + +#ifdef TOOLS_ENABLED +#include "editor/editor_internal_calls.h" +#include "editor/script_templates/templates.gen.h" +#endif #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" @@ -39,36 +55,22 @@ #include "core/os/mutex.h" #include "core/os/os.h" #include "core/os/thread.h" +#include "servers/text_server.h" #ifdef TOOLS_ENABLED #include "core/os/keyboard.h" #include "editor/editor_file_system.h" -#include "editor/editor_internal_calls.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/inspector_dock.h" #include "editor/node_dock.h" -#include "editor/script_templates/templates.gen.h" #endif -#ifdef DEBUG_METHODS_ENABLED -#include "class_db_api_json.h" -#endif - -#include "godotsharp_dirs.h" -#include "managed_callable.h" -#include "mono_gd/gd_mono_cache.h" -#include "servers/text_server.h" -#include "signal_awaiter_utils.h" -#include "utils/macros.h" -#include "utils/naming_utils.h" -#include "utils/string_utils.h" - -#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var) +#include <stdint.h> // Types that will be skipped over (in favor of their base types) when setting up instance bindings. // This must be a superset of `ignored_types` in bindings_generator.cpp. -const Vector<String> ignored_types = { "PhysicsServer2DExtension", "PhysicsServer3DExtension" }; +const Vector<String> ignored_types = {}; #ifdef TOOLS_ENABLED static bool _create_project_solution_if_needed() { @@ -116,18 +118,19 @@ void CSharpLanguage::init() { GLOBAL_DEF("dotnet/project/assembly_name", ""); #ifdef TOOLS_ENABLED GLOBAL_DEF("dotnet/project/solution_directory", ""); + 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() { @@ -329,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 @@ -393,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; } @@ -436,6 +440,11 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { 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"; } @@ -550,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(); } @@ -583,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 { @@ -744,11 +754,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { return false; // Already up to date } } else { - String assembly_name = GLOBAL_GET("dotnet/project/assembly_name"); - - if (assembly_name.is_empty()) { - assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); - } + String assembly_name = path::get_csharp_project_name(); assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() .path_join(assembly_name + ".dll"); @@ -774,10 +780,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } - // TODO: - // Currently, this reloads all scripts, including those whose class is not part of the - // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. - print_verbose(".NET: Reloading assemblies..."); // There is no soft reloading with Mono. It's always hard reloading. @@ -788,8 +790,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { MutexLock lock(script_instances_mutex); for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) { - // Cast to CSharpScript to avoid being erased by accident - scripts.push_back(Ref<CSharpScript>(elem->self())); + // Do not reload scripts with only non-collectible instances to avoid disrupting event subscriptions and such. + bool is_reloadable = elem->self()->instances.size() == 0; + for (Object *obj : elem->self()->instances) { + ERR_CONTINUE(!obj->get_script_instance()); + CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); + if (GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(csi->get_gchandle_intptr())) { + is_reloadable = true; + break; + } + } + if (is_reloadable) { + // Cast to CSharpScript to avoid being erased by accident. + scripts.push_back(Ref<CSharpScript>(elem->self())); + } } } @@ -804,6 +818,10 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr); + if (!GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(managed_callable->delegate_handle)) { + continue; + } + Array serialized_data; bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle( @@ -911,6 +929,15 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->_clear(); } + // Release the delegates that were serialized earlier. + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (KeyValue<ManagedCallable *, Array> &kv : ManagedCallable::instances_pending_reload) { + kv.key->release_delegate_handle(); + } + } + // Do domain reload if (gdmono->reload_project_assemblies() != OK) { // Failed to reload the scripts domain @@ -1162,19 +1189,6 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { } } -void CSharpLanguage::_on_scripts_domain_about_to_unload() { -#ifdef GD_MONO_HOT_RELOAD - { - MutexLock lock(ManagedCallable::instances_mutex); - - for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { - ManagedCallable *managed_callable = elem->self(); - managed_callable->release_delegate_handle(); - } - } -#endif -} - #ifdef TOOLS_ENABLED void CSharpLanguage::_editor_init_callback() { // Load GodotTools and initialize GodotSharpEditor @@ -1192,8 +1206,6 @@ void CSharpLanguage::_editor_init_callback() { // Add plugin to EditorNode and enable it EditorNode::add_editor_plugin(godotsharp_editor); - ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B); - ED_SHORTCUT_OVERRIDE("mono/build_solution", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::B); godotsharp_editor->enable_plugin(); get_singleton()->godotsharp_editor = godotsharp_editor; @@ -1653,7 +1665,8 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { } } - for (const PropertyInfo &prop : props) { + for (PropertyInfo &prop : props) { + validate_property(prop); p_properties->push_back(prop); } } @@ -1682,7 +1695,7 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const { Variant ret; Callable::CallError call_error; GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( - gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret); + gchandle.get_intptr(), &SNAME("_property_can_revert"), args, 1, &call_error, &ret); if (call_error.error != Callable::CallError::CALL_OK) { return false; @@ -1691,6 +1704,24 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const { return (bool)ret; } +void CSharpInstance::validate_property(PropertyInfo &p_property) const { + ERR_FAIL_COND(!script.is_valid()); + + Variant property_arg = (Dictionary)p_property; + const Variant *args[1] = { &property_arg }; + + Variant ret; + Callable::CallError call_error; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &SNAME("_validate_property"), args, 1, &call_error, &ret); + + if (call_error.error != Callable::CallError::CALL_OK) { + return; + } + + p_property = PropertyInfo::from_dict(property_arg); +} + bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { ERR_FAIL_COND_V(!script.is_valid(), false); @@ -1700,7 +1731,7 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re Variant ret; Callable::CallError call_error; GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( - gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret); + gchandle.get_intptr(), &SNAME("_property_get_revert"), args, 1, &call_error, &ret); if (call_error.error != Callable::CallError::CALL_OK) { return false; @@ -1956,7 +1987,7 @@ const Variant CSharpInstance::get_rpc_config() const { return script->get_rpc_config(); } -void CSharpInstance::notification(int p_notification) { +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 @@ -1974,7 +2005,7 @@ void CSharpInstance::notification(int p_notification) { return; } - _call_notification(p_notification); + _call_notification(p_notification, p_reversed); GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( gchandle.get_intptr(), /* okIfNull */ false); @@ -1982,19 +2013,17 @@ void CSharpInstance::notification(int p_notification) { return; } - _call_notification(p_notification); + _call_notification(p_notification, p_reversed); } -void CSharpInstance::_call_notification(int p_notification) { +void CSharpInstance::_call_notification(int p_notification, bool p_reversed) { Variant arg = p_notification; const Variant *args[1] = { &arg }; - StringName method_name = SNAME("_notification"); - - Callable::CallError call_error; Variant ret; + Callable::CallError call_error; GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( - gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret); + gchandle.get_intptr(), &SNAME("_notification"), args, 1, &call_error, &ret); } String CSharpInstance::to_string(bool *r_valid) { @@ -2218,7 +2247,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda } bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { - if (p_name == CSharpLanguage::singleton->string_names._script_source) { + if (p_name == SNAME("script/source")) { r_ret = get_source_code(); return true; } @@ -2227,7 +2256,7 @@ bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { } bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == CSharpLanguage::singleton->string_names._script_source) { + if (p_name == SNAME("script/source")) { set_source_code(p_value); reload(); return true; @@ -2237,7 +2266,7 @@ bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) { } void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const { - p_properties->push_back(PropertyInfo(Variant::STRING, CSharpLanguage::singleton->string_names._script_source, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_properties->push_back(PropertyInfo(Variant::STRING, SNAME("script/source"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } void CSharpScript::_bind_methods() { @@ -2267,7 +2296,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { // 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(); - if (efs) { + if (efs && !p_script->get_path().is_empty()) { efs->update_file(p_script->get_path()); } #endif @@ -2277,6 +2306,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; @@ -2290,12 +2320,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(); @@ -2324,9 +2355,14 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { Variant::Type param_type = (Variant::Type)(int)param["type"]; PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); arg_info.usage = (uint32_t)param["usage"]; + if (param.has("class_name")) { + arg_info.class_name = (StringName)param["class_name"]; + } mi.arguments.push_back(arg_info); } + mi.flags = (uint32_t)method_info_dict["flags"]; + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } @@ -2354,6 +2390,9 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { Variant::Type param_type = (Variant::Type)(int)param["type"]; PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); arg_info.usage = (uint32_t)param["usage"]; + if (param.has("class_name")) { + arg_info.class_name = (StringName)param["class_name"]; + } mi.arguments.push_back(arg_info); } @@ -2377,7 +2416,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 { @@ -2573,6 +2612,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) { + ERR_FAIL_COND_V(!valid, Variant()); + + 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; @@ -2878,9 +2929,3 @@ void ResourceFormatSaverCSharpScript::get_recognized_extensions(const Ref<Resour bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) const { return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr; } - -CSharpLanguage::StringNameCache::StringNameCache() { - _property_can_revert = StaticCString::create("_property_can_revert"); - _property_get_revert = StaticCString::create("_property_get_revert"); - _script_source = StaticCString::create("script/source"); -} |