diff options
author | Nils ANDRÉ-CHANG <nils@nilsand.re> | 2019-09-12 21:28:49 +0100 |
---|---|---|
committer | Nils ANDRÉ-CHANG <nils@nilsand.re> | 2019-09-26 20:36:12 +0100 |
commit | 0024dd7bb5a8a5194ed0283fc506edcd8b4a7737 (patch) | |
tree | 86316cccbf4fda58a275a7451e37fda83465bb20 /modules/mono | |
parent | cafb888361eba08297dd88b18dc71f4d418525c0 (diff) | |
parent | 24e1039eb6fe32115e8d1a62a84965e9be19a2ed (diff) |
Merge branch 'master' into tab_key
Diffstat (limited to 'modules/mono')
87 files changed, 2374 insertions, 748 deletions
diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore new file mode 100644 index 0000000000..fa6d00cbbb --- /dev/null +++ b/modules/mono/.gitignore @@ -0,0 +1,2 @@ +# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore. +!*.sln diff --git a/modules/mono/SCsub b/modules/mono/SCsub index cc60e64a11..a9afa7ccf6 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -8,13 +8,7 @@ Import('env_modules') env_mono = env_modules.Clone() -env_mono.add_source_files(env.modules_sources, '*.cpp') -env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') -env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') -env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') - if env['tools']: - env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') # NOTE: It is safe to generate this file here, since this is still executed serially import build_scripts.make_cs_compressed_header as make_cs_compressed_header make_cs_compressed_header.generate_header( @@ -62,3 +56,13 @@ if env_mono['tools']: # GodotTools.ProjectEditor which doesn't depend on the Godot API solution and # is required by the bindings generator in order to be able to generated it. godot_tools_build.build_project_editor_only(env_mono) + +# Add sources + +env_mono.add_source_files(env.modules_sources, '*.cpp') +env_mono.add_source_files(env.modules_sources, 'glue/*.cpp') +env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp') +env_mono.add_source_files(env.modules_sources, 'utils/*.cpp') + +if env['tools']: + env_mono.add_source_files(env.modules_sources, 'editor/*.cpp') diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index c47cfc8a38..35daa6d307 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -84,10 +84,16 @@ def build(env_mono): source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll'] sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames] - target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + target_filenames = [ + 'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll', + 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll' + ] if env_mono['target'] == 'debug': - target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + target_filenames += [ + 'GodotTools.pdb', 'GodotTools.IdeConnection.pdb', 'GodotTools.BuildLogger.pdb', + 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb' + ] targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py index cd9210897d..8cad204d7b 100644 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -3,23 +3,6 @@ def generate_compressed_config(config_src, output_dir): import os.path from compat import byte_to_str - # Header file - with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header: - header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ -#ifndef ANDROID_MONO_CONFIG_GEN_H -#define ANDROID_MONO_CONFIG_GEN_H - -#ifdef ANDROID_ENABLED - -#include "core/ustring.h" - -String get_godot_android_mono_config(); - -#endif // ANDROID_ENABLED - -#endif // ANDROID_MONO_CONFIG_GEN_H -''') - # Source file with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: with open(config_src, 'rb') as f: @@ -36,7 +19,7 @@ String get_godot_android_mono_config(); bytes_seq_str += byte_to_str(buf[buf_idx]) cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "android_mono_config.gen.h" +#include "android_mono_config.h" #ifdef ANDROID_ENABLED diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 9f0eb58896..f751719531 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -113,8 +113,8 @@ def configure(env, env_mono): else: env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)) - env.Append(LIBS='psapi') - env.Append(LIBS='version') + env.Append(LIBS=['psapi']) + env.Append(LIBS=['version']) else: mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') @@ -124,7 +124,7 @@ def configure(env, env_mono): if env.msvc: env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) else: - env.Append(LIBS=mono_lib_name) + env.Append(LIBS=[mono_lib_name]) mono_bin_path = os.path.join(mono_root, 'bin') diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index 4a6637434a..7580911a0a 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 */ diff --git a/modules/mono/class_db_api_json.h b/modules/mono/class_db_api_json.h index ddfe2debea..9888ecfb55 100644 --- a/modules/mono/class_db_api_json.h +++ b/modules/mono/class_db_api_json.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 */ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 078a490b22..e14e919f92 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -115,7 +115,7 @@ void CSharpLanguage::init() { gdmono->initialize(); #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - // Generate bindings here, before loading assemblies. `initialize_load_assemblies` aborts + // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts // the applications if the api assemblies or the main tools assembly is missing, but this // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); @@ -123,7 +123,7 @@ void CSharpLanguage::init() { #endif #ifndef MONO_GLUE_ENABLED - print_line("Run this binary with `--generate-mono-glue path/to/modules/mono/glue`"); + print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); #endif gdmono->initialize_load_assemblies(); @@ -629,7 +629,6 @@ void CSharpLanguage::frame() { if (exc) { GDMonoUtils::debug_unhandled_exception(exc); - GD_UNREACHABLE(); } } } @@ -1037,6 +1036,7 @@ void CSharpLanguage::_load_scripts_metadata() { String old_json; Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + ERR_FAIL_COND(ferr != OK); Variant old_dict_var; @@ -1044,7 +1044,7 @@ void CSharpLanguage::_load_scripts_metadata() { int err_line; Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); if (json_err != OK) { - ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")"); + ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); return; } @@ -1054,7 +1054,7 @@ void CSharpLanguage::_load_scripts_metadata() { print_verbose("Successfully loaded scripts metadata"); } else { if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file"); + ERR_PRINT("Missing scripts metadata file."); } } } @@ -1101,7 +1101,7 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S _debug_parse_err_line = p_line; _debug_parse_err_file = p_file; _debug_error = p_error; - ScriptDebugger::get_singleton()->debug(this, false); + ScriptDebugger::get_singleton()->debug(this, false, true); return true; } else { return false; @@ -1769,12 +1769,8 @@ MonoObject *CSharpInstance::_internal_new_managed() { // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - if (ctor == NULL) { - ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + script->get_path()); - - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); - } + ERR_FAIL_NULL_V_MSG(ctor, NULL, + "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); @@ -1793,8 +1789,7 @@ MonoObject *CSharpInstance::_internal_new_managed() { owner = NULL; - ERR_EXPLAIN("Failed to allocate memory for the object"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); } // Tie managed to unmanaged @@ -2234,7 +2229,7 @@ bool CSharpScript::_update_exports() { MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject"); + ERR_PRINT("Failed to allocate temporary MonoObject."); return false; } @@ -2242,12 +2237,8 @@ bool CSharpScript::_update_exports() { GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - if (ctor == NULL) { - ERR_PRINTS("Cannot construct temporary MonoObject because the class does not define a parameterless constructor: " + get_path()); - - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); - } + ERR_FAIL_NULL_V_MSG(ctor, NULL, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); MonoException *ctor_exc = NULL; ctor->invoke(tmp_object, NULL, &ctor_exc); @@ -2400,7 +2391,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve arg.type = GDMonoMarshal::managed_to_variant_type(types[i]); if (arg.type == Variant::NIL) { - ERR_PRINTS("Unknown type of signal parameter: " + arg.name + " in " + p_class->get_full_name()); + ERR_PRINTS("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); return false; } @@ -2428,7 +2419,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->is_static()) { if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) - ERR_PRINTS("Cannot export member because it is static: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } @@ -2451,12 +2442,12 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); if (!property->has_getter()) { if (exported) - ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } if (!property->has_setter()) { if (exported) - ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } } @@ -2475,16 +2466,15 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect String hint_string; if (variant_type == Variant::NIL) { - ERR_PRINTS("Unknown exported member type: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); + ERR_PRINTS("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); return false; } int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); - if (hint_res == -1) { - ERR_EXPLAIN("Error while trying to determine information about the exported member: " + MEMBER_FULL_QUALIFIED_NAME(p_member)); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(hint_res == -1, false, + "Error while trying to determine information about the exported member: '" + + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); if (hint_res == 0) { hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); @@ -2533,17 +2523,11 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL); - if (val_obj == NULL) { - ERR_EXPLAIN("Failed to get '" + enum_field_name + "' constant enum value"); - ERR_FAIL_V(-1); - } + ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); bool r_error; uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); - if (r_error) { - ERR_EXPLAIN("Failed to unbox '" + enum_field_name + "' constant enum value"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(r_error, -1, "Failed to unbox '" + enum_field_name + "' constant enum value."); if (val != (unsigned int)i) { uses_default_values = false; @@ -2578,17 +2562,11 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage PropertyHint elem_hint = PROPERTY_HINT_NONE; String elem_hint_string; - if (elem_variant_type == Variant::NIL) { - ERR_EXPLAIN("Unknown array element type"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type."); int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string); - if (hint_res == -1) { - ERR_EXPLAIN("Error while trying to determine information about the array element type"); - ERR_FAIL_V(-1); - } + ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type."); // Format: type/hint:hint_string r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string; @@ -2683,7 +2661,7 @@ void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const { void CSharpScript::_bind_methods() { - ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo(Variant::OBJECT, "new")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { @@ -2776,7 +2754,7 @@ bool CSharpScript::can_instance() const { "Compile", ProjectSettings::get_singleton()->globalize_path(get_path())); } else { - ERR_PRINTS("Cannot add " + get_path() + " to the C# project because it could not be created."); + ERR_PRINTS("C# project could not be created; cannot add file: '" + get_path() + "'."); } } } @@ -2794,12 +2772,10 @@ bool CSharpScript::can_instance() const { if (extra_cond && !script_class) { if (GDMono::get_singleton()->get_project_assembly() == NULL) { // The project assembly is not loaded - ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); } else { // The project assembly is loaded, but the class could not found - ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); } } @@ -2821,14 +2797,12 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg // Search the constructor first, to fail with an error if it's not found before allocating anything else. GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); if (ctor == NULL) { - if (p_argcount == 0) { - String path = get_path(); - ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() + - "' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path)); - } + ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL, + "Cannot create script instance. The class '" + script_class->get_full_name() + + "' does not define a parameterless constructor." + + (get_path().empty() ? String() : " Path: '" + get_path() + "'.")); - ERR_EXPLAIN("Constructor not found"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Constructor not found."); } Ref<Reference> ref; @@ -2879,8 +2853,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg p_owner->set_script_instance(NULL); r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_EXPLAIN("Failed to allocate memory for the object"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object."); } // Tie managed to unmanaged @@ -2914,11 +2887,10 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call r_error.error = Variant::CallError::CALL_OK; REF ref; - Object *owner = NULL; ERR_FAIL_NULL_V(native, Variant()); - owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); Reference *r = Object::cast_to<Reference>(owner); if (r) { @@ -2952,8 +2924,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { if (ScriptDebugger::get_singleton()) { CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } - ERR_EXPLAIN("Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name + + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'."); } } @@ -3205,12 +3177,12 @@ int CSharpScript::get_member_line(const StringName &p_member) const { Error CSharpScript::load_source_code(const String &p_path) { Error ferr = read_all_file_utf8(p_path, source); - if (ferr != OK) { - if (ferr == ERR_INVALID_DATA) { - ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - } - ERR_FAIL_V(ferr); - } + + 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." + " Please ensure that scripts are saved in valid UTF-8 unicode." : + "Failed to read file: '" + p_path + "'."); #ifdef TOOLS_ENABLED source_changed_cache = true; @@ -3279,8 +3251,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p #ifdef DEBUG_ENABLED // User is responsible for thread attach/detach - ERR_EXPLAIN("Thread is not attached"); - CRASH_COND(mono_domain_get() == NULL); + CRASH_COND_MSG(mono_domain_get() == NULL, "Thread is not attached."); #endif #endif @@ -3347,8 +3318,7 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r "Compile", ProjectSettings::get_singleton()->globalize_path(p_path)); } else { - ERR_PRINTS("Failed to create C# project"); - ERR_PRINTS("Cannot add " + p_path + " to the C# project"); + ERR_PRINTS("C# project could not be created; cannot add file: '" + p_path + "'."); } } #endif diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index f3ac353c0f..dcfdd83831 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -11,6 +11,7 @@ <AssemblyName>GodotTools.BuildLogger</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index f36b40f87c..24c7cb1573 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -8,6 +8,7 @@ <RootNamespace>GodotTools.Core</RootNamespace> <AssemblyName>GodotTools.Core</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs new file mode 100644 index 0000000000..7a2ff2ca56 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs @@ -0,0 +1,33 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public class ConsoleLogger : ILogger + { + public void LogDebug(string message) + { + Console.WriteLine("DEBUG: " + message); + } + + public void LogInfo(string message) + { + Console.WriteLine("INFO: " + message); + } + + public void LogWarning(string message) + { + Console.WriteLine("WARN: " + message); + } + + public void LogError(string message) + { + Console.WriteLine("ERROR: " + message); + } + + public void LogError(string message, Exception e) + { + Console.WriteLine("EXCEPTION: " + message); + Console.WriteLine(e); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs new file mode 100644 index 0000000000..be89638241 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs @@ -0,0 +1,94 @@ +using System; +using Path = System.IO.Path; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeBase : IDisposable + { + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private readonly string projectMetadataDir; + + protected const string MetaFileName = "ide_server_meta.txt"; + protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName); + + private GodotIdeConnection connection; + protected readonly object ConnectionLock = new object(); + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected; + + public event Action Connected + { + add + { + if (connection != null && !connection.IsDisposed) + connection.Connected += value; + } + remove + { + if (connection != null && !connection.IsDisposed) + connection.Connected -= value; + } + } + + protected GodotIdeConnection Connection + { + get => connection; + set + { + connection?.Dispose(); + connection = value; + } + } + + protected GodotIdeBase(string projectMetadataDir) + { + this.projectMetadataDir = projectMetadataDir; + } + + protected void DisposeConnection() + { + lock (ConnectionLock) + { + connection?.Dispose(); + } + } + + ~GodotIdeBase() + { + Dispose(disposing: false); + } + + public void Dispose() + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) // lock may not be fair + return; + IsDisposed = true; + } + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + connection?.Dispose(); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs new file mode 100644 index 0000000000..4f56a8d71b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeClient : GodotIdeBase + { + protected GodotIdeMetadata GodotIdeMetadata; + + private readonly FileSystemWatcher fsWatcher; + + protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + // FileSystemWatcher requires an existing directory + if (!File.Exists(projectMetadataDir)) + Directory.CreateDirectory(projectMetadataDir); + + fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName); + } + + private void OnMetaFileChanged(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null && metadata != GodotIdeMetadata) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private void OnMetaFileDeleted(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + if (IsConnected) + DisposeConnection(); + + // The file may have been re-created + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (IsConnected || !File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private GodotIdeMetadata? ReadMetadataFile() + { + using (var reader = File.OpenText(MetaFilePath)) + { + string portStr = reader.ReadLine(); + + if (portStr == null) + return null; + + string editorExecutablePath = reader.ReadLine(); + + if (editorExecutablePath == null) + return null; + + if (!int.TryParse(portStr, out int port)) + return null; + + return new GodotIdeMetadata(port, editorExecutablePath); + } + } + + private void ConnectToServer() + { + var tcpClient = new TcpClient(); + + Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage); + Connection.Logger = Logger; + + try + { + Logger.LogInfo("Connecting to Godot Ide Server"); + + tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port); + + Logger.LogInfo("Connection open with Godot Ide Server"); + + var clientThread = new Thread(Connection.Start) + { + IsBackground = true, + Name = "Godot Ide Connection Client" + }; + clientThread.Start(); + } + catch (SocketException e) + { + if (e.SocketErrorCode == SocketError.ConnectionRefused) + Logger.LogError("The connection to the Godot Ide Server was refused"); + else + throw; + } + } + + public void Start() + { + Logger.LogInfo("Starting Godot Ide Client"); + + fsWatcher.Changed += OnMetaFileChanged; + fsWatcher.Deleted += OnMetaFileDeleted; + fsWatcher.EnableRaisingEvents = true; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + { + Logger.LogInfo("There is no Godot Ide Server running"); + return; + } + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + else + { + Logger.LogError("Failed to read Godot Ide metadata file"); + } + } + } + + public bool WriteMessage(Message message) + { + return Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + fsWatcher?.Dispose(); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["OpenFile"] = args => + { + switch (args.Length) + { + case 1: + OpenFile(file: args[0]); + return; + case 2: + OpenFile(file: args[0], line: int.Parse(args[1])); + return; + case 3: + OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2])); + return; + default: + throw new ArgumentException(); + } + } + }; + } + + protected abstract void OpenFile(string file); + protected abstract void OpenFile(string file, int line); + protected abstract void OpenFile(string file, int line, int column); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs new file mode 100644 index 0000000000..e7e81f175e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs @@ -0,0 +1,207 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net.Sockets; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeConnection : IDisposable + { + protected const string Version = "1.0"; + + protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}"; + protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}"; + + private const int ClientWriteTimeout = 8000; + private readonly TcpClient tcpClient; + + private TextReader clientReader; + private TextWriter clientWriter; + + private readonly object writeLock = new object(); + + private readonly Func<Message, bool> messageHandler; + + public event Action Connected; + + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected; + + protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler) + { + this.tcpClient = tcpClient; + this.messageHandler = messageHandler; + } + + public void Start() + { + try + { + if (!StartConnection()) + return; + + string messageLine; + while ((messageLine = ReadLine()) != null) + { + if (!MessageParser.TryParse(messageLine, out Message msg)) + { + Logger.LogError($"Received message with invalid format: {messageLine}"); + continue; + } + + Logger.LogDebug($"Received message: {msg}"); + + if (msg.Id == "close") + { + Logger.LogInfo("Closing connection"); + return; + } + + try + { + try + { + Debug.Assert(messageHandler != null); + + if (!messageHandler(msg)) + Logger.LogError($"Received unknown message: {msg}"); + } + catch (Exception e) + { + Logger.LogError($"Message handler for '{msg}' failed with exception", e); + } + } + catch (Exception e) + { + Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + } + } + } + catch (Exception e) + { + Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e); + } + finally + { + Dispose(); + } + } + + private bool StartConnection() + { + NetworkStream clientStream = tcpClient.GetStream(); + + clientReader = new StreamReader(clientStream, Encoding.UTF8); + + lock (writeLock) + clientWriter = new StreamWriter(clientStream, Encoding.UTF8); + + clientStream.WriteTimeout = ClientWriteTimeout; + + if (!WriteHandshake()) + { + Logger.LogError("Could not write handshake"); + return false; + } + + if (!IsValidResponseHandshake(ReadLine())) + { + Logger.LogError("Received invalid handshake"); + return false; + } + + Connected?.Invoke(); + + Logger.LogInfo("Godot Ide connection started"); + + return true; + } + + private string ReadLine() + { + try + { + return clientReader?.ReadLine(); + } + catch (Exception e) + { + if (IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Interrupted) + return null; + } + + throw; + } + } + + public bool WriteMessage(Message message) + { + Logger.LogDebug($"Sending message {message}"); + + var messageComposer = new MessageComposer(); + + messageComposer.AddArgument(message.Id); + foreach (string argument in message.Arguments) + messageComposer.AddArgument(argument); + + return WriteLine(messageComposer.ToString()); + } + + protected bool WriteLine(string text) + { + if (clientWriter == null || IsDisposed || !IsConnected) + return false; + + lock (writeLock) + { + try + { + clientWriter.WriteLine(text); + clientWriter.Flush(); + } + catch (Exception e) + { + if (!IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Shutdown) + Logger.LogInfo("Client disconnected ungracefully"); + else + Logger.LogError("Exception thrown when trying to write to client", e); + + Dispose(); + } + } + } + + return true; + } + + protected abstract bool WriteHandshake(); + protected abstract bool IsValidResponseHandshake(string handshakeLine); + + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + clientReader?.Dispose(); + clientWriter?.Dispose(); + ((IDisposable) tcpClient)?.Dispose(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs new file mode 100644 index 0000000000..1b11a14358 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionClient : GodotIdeConnection + { + public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ClientHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ServerHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs new file mode 100644 index 0000000000..aa98dc7ca3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionServer : GodotIdeConnection + { + public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ServerHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ClientHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs new file mode 100644 index 0000000000..d16daba0e2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs @@ -0,0 +1,45 @@ +namespace GodotTools.IdeConnection +{ + public struct GodotIdeMetadata + { + public int Port { get; } + public string EditorExecutablePath { get; } + + public GodotIdeMetadata(int port, string editorExecutablePath) + { + Port = port; + EditorExecutablePath = editorExecutablePath; + } + + public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b) + { + return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath; + } + + public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj is GodotIdeMetadata metadata) + return metadata == this; + + return false; + } + + public bool Equals(GodotIdeMetadata other) + { + return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath; + } + + public override int GetHashCode() + { + unchecked + { + return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj new file mode 100644 index 0000000000..94e525715b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.IdeConnection</RootNamespace> + <AssemblyName>GodotTools.IdeConnection</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <LangVersion>7</LangVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ConsoleLogger.cs" /> + <Compile Include="GodotIdeMetadata.cs" /> + <Compile Include="GodotIdeBase.cs" /> + <Compile Include="GodotIdeClient.cs" /> + <Compile Include="GodotIdeConnection.cs" /> + <Compile Include="GodotIdeConnectionClient.cs" /> + <Compile Include="GodotIdeConnectionServer.cs" /> + <Compile Include="ILogger.cs" /> + <Compile Include="Message.cs" /> + <Compile Include="MessageComposer.cs" /> + <Compile Include="MessageParser.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs new file mode 100644 index 0000000000..614bb30271 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs @@ -0,0 +1,13 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public interface ILogger + { + void LogDebug(string message); + void LogInfo(string message); + void LogWarning(string message); + void LogError(string message); + void LogError(string message, Exception e); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs new file mode 100644 index 0000000000..f24d324ae3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs @@ -0,0 +1,21 @@ +using System.Linq; + +namespace GodotTools.IdeConnection +{ + public struct Message + { + public string Id { get; set; } + public string[] Arguments { get; set; } + + public Message(string id, params string[] arguments) + { + Id = id; + Arguments = arguments; + } + + public override string ToString() + { + return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs new file mode 100644 index 0000000000..9e4cd6ec1a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public class MessageComposer + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + + private static readonly char[] CharsToEscape = { '\\', '"' }; + + public void AddArgument(string argument) + { + AddArgument(argument, quoted: argument.Contains(",")); + } + + public void AddArgument(string argument, bool quoted) + { + if (stringBuilder.Length > 0) + stringBuilder.Append(','); + + if (quoted) + { + stringBuilder.Append('"'); + + foreach (char @char in argument) + { + if (CharsToEscape.Contains(@char)) + stringBuilder.Append('\\'); + stringBuilder.Append(@char); + } + + stringBuilder.Append('"'); + } + else + { + stringBuilder.Append(argument); + } + } + + public override string ToString() + { + return stringBuilder.ToString(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs new file mode 100644 index 0000000000..ed691e481f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public static class MessageParser + { + public static bool TryParse(string messageLine, out Message message) + { + var arguments = new List<string>(); + var stringBuilder = new StringBuilder(); + + bool expectingArgument = true; + + for (int i = 0; i < messageLine.Length; i++) + { + char @char = messageLine[i]; + + if (@char == ',') + { + if (expectingArgument) + arguments.Add(string.Empty); + + expectingArgument = true; + continue; + } + + bool quoted = false; + + if (messageLine[i] == '"') + { + quoted = true; + i++; + } + + while (i < messageLine.Length) + { + @char = messageLine[i]; + + if (quoted && @char == '"') + { + i++; + break; + } + + if (@char == '\\') + { + i++; + if (i < messageLine.Length) + break; + + stringBuilder.Append(messageLine[i]); + } + else if (!quoted && @char == ',') + { + break; // We don't increment the counter to allow the colon to be parsed after this + } + else + { + stringBuilder.Append(@char); + } + + i++; + } + + arguments.Add(stringBuilder.ToString()); + stringBuilder.Clear(); + + expectingArgument = false; + } + + if (arguments.Count == 0) + { + message = new Message(); + return false; + } + + message = new Message + { + Id = arguments[0], + Arguments = arguments.Skip(1).ToArray() + }; + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c7c00e66a2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.IdeConnection")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 08b8ba3946..ab3a5d1aea 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -9,6 +9,7 @@ <AssemblyName>GodotTools.ProjectEditor</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -36,9 +37,8 @@ entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. We make SCons restore the NuGet packages in the project directory instead in this case. --> - <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> + <HintPath Condition=" '$(SolutionDir)' != '' And Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath Condition=" '$(SolutionDir)' == '' Or !Exists('$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll') ">$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 1edc426e00..233aab45b3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -85,7 +85,7 @@ namespace GodotTools.ProjectEditor void AddPropertyIfNotPresent(string name, string condition, string value) { if (root.PropertyGroups - .Any(g => g.Condition == string.Empty || g.Condition == condition && + .Any(g => (g.Condition == string.Empty || g.Condition == condition) && g.Properties .Any(p => p.Name == name && p.Value == value && diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index 6f7d44bec2..a3438ea5f3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,5 +33,9 @@ Global {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 53ff0891d5..44813f962c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -9,7 +9,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBottomPanel : VBoxContainer + public class BottomPanel : VBoxContainer { private EditorInterface editorInterface; @@ -34,7 +34,7 @@ namespace GodotTools for (int i = 0; i < buildTabs.GetChildCount(); i++) { - var tab = (MonoBuildTab) buildTabs.GetChild(i); + var tab = (BuildTab) buildTabs.GetChild(i); if (tab == null) continue; @@ -49,11 +49,11 @@ namespace GodotTools itemTooltip += "\nStatus: "; if (tab.BuildExited) - itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored"; else itemTooltip += "Running"; - if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error) itemTooltip += $"\nErrors: {tab.ErrorCount}"; itemTooltip += $"\nWarnings: {tab.WarningCount}"; @@ -68,15 +68,15 @@ namespace GodotTools } } - public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + public BuildTab GetBuildTabFor(BuildInfo buildInfo) { - foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren())) { if (buildTab.BuildInfo.Equals(buildInfo)) return buildTab; } - var newBuildTab = new MonoBuildTab(buildInfo); + var newBuildTab = new BuildTab(buildInfo); AddBuildTab(newBuildTab); return newBuildTab; @@ -120,7 +120,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.WarningsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -132,7 +132,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.ErrorsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -145,7 +145,7 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) { @@ -166,7 +166,7 @@ namespace GodotTools Internal.GodotIs32Bits() ? "32" : "64" }; - bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines); if (!buildSuccess) return; @@ -193,9 +193,9 @@ namespace GodotTools int selectedItem = selectedItems[0]; - var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem); - OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName)); } public override void _Notification(int what) @@ -211,13 +211,13 @@ namespace GodotTools } } - public void AddBuildTab(MonoBuildTab buildTab) + public void AddBuildTab(BuildTab buildTab) { buildTabs.AddChild(buildTab); RaiseBuildTab(buildTab); } - public void RaiseBuildTab(MonoBuildTab buildTab) + public void RaiseBuildTab(BuildTab buildTab) { if (buildTab.GetParent() != buildTabs) throw new InvalidOperationException("Build tab is not in the tabs list"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index f849356919..9a2b2e3a26 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -46,8 +46,8 @@ namespace GodotTools.Build { if (OS.IsWindows()) { - return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") - == GodotSharpBuilds.BuildTool.MsBuildMono; + return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == BuildManager.BuildTool.MsBuildMono; } return false; @@ -103,16 +103,16 @@ namespace GodotTools.Build return process; } - public static int Build(MonoBuildInfo monoBuildInfo) + public static int Build(BuildInfo buildInfo) { - return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return Build(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } - public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + public static async Task<int> BuildAsync(BuildInfo buildInfo) { - return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return await BuildAsync(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) @@ -137,7 +137,7 @@ namespace GodotTools.Build private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties) { - string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " + + string arguments = $@"""{solution}"" /v:normal /t:Build ""/p:{"Configuration=" + config}"" " + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; foreach (string customProperty in customProperties) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index f0068385f4..4c1e47ecad 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -15,18 +15,17 @@ namespace GodotTools.Build { private static string _msbuildToolsPath = string.Empty; private static string _msbuildUnixPath = string.Empty; - private static string _xbuildUnixPath = string.Empty; public static string FindMsBuild() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); if (OS.IsWindows()) { switch (buildTool) { - case GodotSharpBuilds.BuildTool.MsBuildVs: + case BuildManager.BuildTool.MsBuildVs: { if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) { @@ -35,7 +34,7 @@ namespace GodotTools.Build if (_msbuildToolsPath.Empty()) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); } } @@ -44,31 +43,17 @@ namespace GodotTools.Build return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); } - - case GodotSharpBuilds.BuildTool.MsBuildMono: + case BuildManager.BuildTool.MsBuildMono: { string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); if (!File.Exists(msbuildPath)) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); } return msbuildPath; } - - case GodotSharpBuilds.BuildTool.XBuild: - { - string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat"); - - if (!File.Exists(xbuildPath)) - { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}"); - } - - return xbuildPath; - } - default: throw new IndexOutOfRangeException("Invalid build tool in editor settings"); } @@ -76,20 +61,7 @@ namespace GodotTools.Build if (OS.IsUnix()) { - if (buildTool == GodotSharpBuilds.BuildTool.XBuild) - { - if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath)) - { - // Try to search it again if it wasn't found last time or if it was removed from its location - _xbuildUnixPath = FindBuildEngineOnUnix("msbuild"); - } - - if (_xbuildUnixPath.Empty()) - { - throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'"); - } - } - else + if (buildTool == BuildManager.BuildTool.MsBuildMono) { if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) { @@ -99,11 +71,15 @@ namespace GodotTools.Build if (_msbuildUnixPath.Empty()) { - throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'"); } - } - return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath; + return _msbuildUnixPath; + } + else + { + throw new IndexOutOfRangeException("Invalid build tool in editor settings"); + } } throw new PlatformNotSupportedException(); diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs index 858e852392..70bd552f2f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { [Serializable] - public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization { public string Solution { get; } public string Configuration { get; } @@ -17,7 +17,7 @@ namespace GodotTools public override bool Equals(object obj) { - if (obj is MonoBuildInfo other) + if (obj is BuildInfo other) return other.Solution == Solution && other.Configuration == Configuration; return false; @@ -34,11 +34,11 @@ namespace GodotTools } } - private MonoBuildInfo() + private BuildInfo() { } - public MonoBuildInfo(string solution, string configuration) + public BuildInfo(string solution, string configuration) { Solution = solution; Configuration = configuration; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index a884b0ead0..417032da54 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -6,19 +6,16 @@ using GodotTools.Build; using GodotTools.Internals; using GodotTools.Utils; using static GodotTools.Internals.Globals; -using Error = Godot.Error; using File = GodotTools.Utils.File; -using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class GodotSharpBuilds + public static class BuildManager { - private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>(); public const string PropNameMsbuildMono = "MSBuild (Mono)"; public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; - public const string PropNameXbuild = "xbuild (Deprecated)"; public const string MsBuildIssuesFileName = "msbuild_issues.csv"; public const string MsBuildLogFileName = "msbuild_log.txt"; @@ -26,11 +23,10 @@ namespace GodotTools public enum BuildTool { MsBuildMono, - MsBuildVs, - XBuild // Deprecated + MsBuildVs } - private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -40,31 +36,23 @@ namespace GodotTools File.Delete(issuesFile); } - private static string _ApiFolderName(ApiAssemblyType apiType) - { - ulong apiHash = apiType == ApiAssemblyType.Core ? - Internal.GetCoreApiHash() : - Internal.GetEditorApiHash(); - return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; - } - private static void ShowBuildErrorDialog(string message) { GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); - GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + GodotSharpEditor.Instance.BottomPanel.ShowBuildTab(); } - public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); - public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException(); - private static string GetLogFilePath(MonoBuildInfo buildInfo) + private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); } - private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + private static string GetIssuesFilePath(BuildInfo buildInfo) { - return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName); + return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName); } private static void PrintVerbose(string text) @@ -73,7 +61,7 @@ namespace GodotTools Godot.GD.Print(text); } - public static bool Build(MonoBuildInfo buildInfo) + public static bool Build(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -82,7 +70,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); buildTab.OnBuildStart(); // Required in order to update the build tasks list @@ -105,7 +93,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -122,7 +110,7 @@ namespace GodotTools } } - public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + public static async Task<bool> BuildAsync(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -131,7 +119,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); try { @@ -150,7 +138,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -167,32 +155,6 @@ namespace GodotTools } } - public static bool BuildApiSolution(string apiSlnDir, string config) - { - string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); - - string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); - string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); - - string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); - string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); - - if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) - return true; // The assemblies are in the output folder; assume the solution is already built - - var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); - - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings - - if (Build(apiBuildInfo)) - return true; - - ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); - return false; - } - public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -202,14 +164,17 @@ namespace GodotTools // case the user decided to delete them at some point after they were loaded. Internal.UpdateApiAssembliesFromPrebuilt(); + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { pr.Step("Building project solution", 0); - var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config); // Add Godot defines - string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; + string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; foreach (var godotDefine in godotDefines) constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; @@ -217,7 +182,7 @@ namespace GodotTools if (Internal.GodotIsRealTDouble()) constants += "GODOT_REAL_T_IS_DOUBLE;"; - constants += OS.IsWindows() ? "\"" : "\\\""; + constants += buildTool == BuildTool.MsBuildVs ? "\"" : "\\\""; buildInfo.CustomProperties.Add(constants); @@ -239,11 +204,28 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest; + + if (currentPlayRequest != null) + { + if (currentPlayRequest.Value.HasDebugger) + { + // Set the environment variable that will tell the player to connect to the IDE debugger + // TODO: We should probably add a better way to do this + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", + "--debugger-agent=transport=dt_socket" + + $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" + + ",server=n"); + } + + return true; // Requested play from an external editor/IDE which already built the project + } + var godotDefines = new[] { Godot.OS.GetName(), @@ -267,8 +249,8 @@ namespace GodotTools ["name"] = "mono/builds/build_tool", ["hint"] = Godot.PropertyHint.Enum, ["hint_string"] = OS.IsWindows() ? - $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" : - $"{PropNameMsbuildMono},{PropNameXbuild}" + $"{PropNameMsbuildMono},{PropNameMsbuildVs}" : + $"{PropNameMsbuildMono}" }); EditorDef("mono/builds/print_build_output", false); diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index 75fdacc0da..807a20d9a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBuildTab : VBoxContainer + public class BuildTab : VBoxContainer { public enum BuildResults { @@ -55,47 +55,54 @@ namespace GodotTools } } - public MonoBuildInfo BuildInfo { get; private set; } + public BuildInfo BuildInfo { get; private set; } private void _LoadIssuesFromFile(string csvFile) { using (var file = new Godot.File()) { - Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); - - if (openError != Error.Ok) - return; - - while (!file.EofReached()) + try { - string[] csvColumns = file.GetCsvLine(); + Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); - if (csvColumns.Length == 1 && csvColumns[0].Empty()) + if (openError != Error.Ok) return; - if (csvColumns.Length != 7) + while (!file.EofReached()) { - GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); - continue; + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && csvColumns[0].Empty()) + return; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var issue = new BuildIssue + { + Warning = csvColumns[0] == "warning", + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6] + }; + + if (issue.Warning) + WarningCount += 1; + else + ErrorCount += 1; + + issues.Add(issue); } - - var issue = new BuildIssue - { - Warning = csvColumns[0] == "warning", - File = csvColumns[1], - Line = int.Parse(csvColumns[2]), - Column = int.Parse(csvColumns[3]), - Code = csvColumns[4], - Message = csvColumns[5], - ProjectFile = csvColumns[6] - }; - - if (issue.Warning) - WarningCount += 1; - else - ErrorCount += 1; - - issues.Add(issue); + } + finally + { + file.Close(); // Disposing it is not enough. We need to call Close() } } } @@ -192,7 +199,7 @@ namespace GodotTools ErrorCount = 0; UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExit(BuildResults result) @@ -200,10 +207,10 @@ namespace GodotTools BuildExited = true; BuildResult = result; - _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExecFailed(string cause) @@ -220,7 +227,7 @@ namespace GodotTools UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void RestartBuild() @@ -228,7 +235,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build already started"); - GodotSharpBuilds.RestartBuild(this); + BuildManager.RestartBuild(this); } public void StopBuild() @@ -236,7 +243,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build is not in progress"); - GodotSharpBuilds.StopBuild(this); + BuildManager.StopBuild(this); } public override void _Ready() @@ -248,11 +255,11 @@ namespace GodotTools AddChild(issuesList); } - private MonoBuildTab() + private BuildTab() { } - public MonoBuildTab(MonoBuildInfo buildInfo) + public BuildTab(BuildInfo buildInfo) { BuildInfo = buildInfo; } diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 4535ed7247..c021a9051e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -9,7 +9,7 @@ using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class CSharpProject + public static class CsProjOperations { public static string GenerateGameProject(string dir, string name) { diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs new file mode 100644 index 0000000000..4312ca0230 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -0,0 +1,11 @@ +namespace GodotTools +{ + public enum ExternalEditorId + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 90dec43412..7da7cff933 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -2,16 +2,18 @@ using Godot; using GodotTools.Utils; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; +using GodotTools.Ides; using GodotTools.Internals; using GodotTools.ProjectEditor; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -using Path = System.IO.Path; using OS = GodotTools.Utils.OS; namespace GodotTools { + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings editorSettings; @@ -24,12 +26,11 @@ namespace GodotTools private ToolButton bottomPanelBtn; - private MonoDevelopInstance monoDevelopInstance; - private MonoDevelopInstance visualStudioForMacInstance; + public GodotIdeManager GodotIdeManager { get; private set; } private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization - public MonoBottomPanel MonoBottomPanel { get; private set; } + public BottomPanel BottomPanel { get; private set; } private bool CreateProjectSolution() { @@ -44,7 +45,7 @@ namespace GodotTools if (name.Empty()) name = "UnnamedProject"; - string guid = CSharpProject.GenerateGameProject(path, name); + string guid = CsProjOperations.GenerateGameProject(path, name); if (guid.Length > 0) { @@ -133,7 +134,7 @@ namespace GodotTools return; // Failed to create solution } - Instance.MonoBottomPanel.BuildProjectPressed(); + Instance.BottomPanel.BuildProjectPressed(); } public override void _Notification(int what) @@ -153,21 +154,12 @@ namespace GodotTools } } - public enum MenuOptions + private enum MenuOptions { CreateSln, AboutCSharp, } - public enum ExternalEditor - { - None, - VisualStudio, // TODO (Windows-only) - VisualStudioForMac, // Mac-only - MonoDevelop, - VsCode - } - public void ShowErrorDialog(string message, string title = "Error") { errorDialog.WindowTitle = title; @@ -184,11 +176,30 @@ namespace GodotTools public Error OpenInExternalEditor(Script script, int line, int col) { - var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor"); switch (editor) { - case ExternalEditor.VsCode: + case ExternalEditorId.None: + // Tells the caller to fallback to the global external editor settings or the built-in editor + return Error.Unavailable; + case ExternalEditorId.VisualStudio: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + GodotIdeManager.SendOpenFile(scriptPath, line + 1, col); + else + GodotIdeManager.SendOpenFile(scriptPath); + + break; + } + + case ExternalEditorId.VsCode: { if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) { @@ -273,38 +284,6 @@ namespace GodotTools break; } - case ExternalEditor.VisualStudioForMac: - goto case ExternalEditor.MonoDevelop; - case ExternalEditor.MonoDevelop: - { - MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) - { - if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) - { - if (visualStudioForMacInstance == null) - visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); - - return visualStudioForMacInstance; - } - - if (monoDevelopInstance == null) - monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); - - return monoDevelopInstance; - } - - string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); - - if (line >= 0) - scriptPath += $";{line + 1};{col}"; - - GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); - - break; - } - - case ExternalEditor.None: - return Error.Unavailable; default: throw new ArgumentOutOfRangeException(); } @@ -314,12 +293,12 @@ namespace GodotTools public bool OverridesExternalEditor() { - return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; } public override bool Build() { - return GodotSharpBuilds.EditorBuildCallback(); + return BuildManager.EditorBuildCallback(); } public override void EnablePlugin() @@ -338,9 +317,9 @@ namespace GodotTools errorDialog = new AcceptDialog(); editorBaseControl.AddChild(errorDialog); - MonoBottomPanel = new MonoBottomPanel(); + BottomPanel = new BottomPanel(); - bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR()); + bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); @@ -380,7 +359,7 @@ namespace GodotTools aboutLabel.Text = "C# support in Godot Engine is in late alpha stage and, while already usable, " + "it is not meant for use in production.\n\n" + - "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Projects can be exported to Linux, macOS, Windows and Android, but not yet to iOS, HTML5 or UWP. " + "Bugs and usability issues will be addressed gradually over future releases, " + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + @@ -398,7 +377,7 @@ namespace GodotTools if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { // Make sure the existing project has Api assembly references configured correctly - CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); } else { @@ -418,25 +397,25 @@ namespace GodotTools AddControlToContainer(CustomControlContainer.Toolbar, buildButton); // External editor settings - EditorDef("mono/editor/external_editor", ExternalEditor.None); + EditorDef("mono/editor/external_editor", ExternalEditorId.None); string settingsHintStr = "Disabled"; if (OS.IsWindows()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsOSX()) { - settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + - $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsUnix()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary @@ -452,7 +431,10 @@ namespace GodotTools AddExportPlugin(exportPlugin); exportPluginWeak = WeakRef(exportPlugin); - GodotSharpBuilds.Initialize(); + BuildManager.Initialize(); + + GodotIdeManager = new GodotIdeManager(); + AddChild(GodotIdeManager); } protected override void Dispose(bool disposing) @@ -469,6 +451,8 @@ namespace GodotTools exportPluginWeak.Dispose(); } + + GodotIdeManager?.Dispose(); } public void OnBeforeSerialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs index b80fe1fab7..4f93ef8530 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -16,7 +16,7 @@ namespace GodotTools { private void AddFile(string srcPath, string dstPath, bool remap = false) { - AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); } public override void _ExportFile(string path, string type, string[] features) @@ -65,14 +65,14 @@ namespace GodotTools string buildConfig = isDebug ? "Debug" : "Release"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); AddFile(scriptsMetadataPath, scriptsMetadataPath); // Turn export features into defines var godotDefines = features; - if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines)) + if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) { GD.PushError("Failed to build project"); return; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 01e8c87d14..3c57900873 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -10,6 +10,7 @@ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotApiConfiguration>Debug</GodotApiConfiguration> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -39,26 +40,31 @@ </ItemGroup> <ItemGroup> <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="ExternalEditorId.cs" /> + <Compile Include="Ides\GodotIdeManager.cs" /> + <Compile Include="Ides\GodotIdeServer.cs" /> + <Compile Include="Ides\MonoDevelop\EditorId.cs" /> + <Compile Include="Ides\MonoDevelop\Instance.cs" /> <Compile Include="Internals\BindingsGenerator.cs" /> <Compile Include="Internals\EditorProgress.cs" /> <Compile Include="Internals\GodotSharpDirs.cs" /> <Compile Include="Internals\Internal.cs" /> <Compile Include="Internals\ScriptClassParser.cs" /> <Compile Include="Internals\Globals.cs" /> - <Compile Include="MonoDevelopInstance.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Build\BuildSystem.cs" /> <Compile Include="Utils\Directory.cs" /> <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\NotifyAwaiter.cs" /> <Compile Include="Utils\OS.cs" /> <Compile Include="GodotSharpEditor.cs" /> - <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="BuildManager.cs" /> <Compile Include="HotReloadAssemblyWatcher.cs" /> - <Compile Include="MonoBuildInfo.cs" /> - <Compile Include="MonoBuildTab.cs" /> - <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="BuildInfo.cs" /> + <Compile Include="BuildTab.cs" /> + <Compile Include="BottomPanel.cs" /> <Compile Include="GodotSharpExport.cs" /> - <Compile Include="CSharpProject.cs" /> + <Compile Include="CsProjOperations.cs" /> <Compile Include="Utils\CollectionExtensions.cs" /> </ItemGroup> <ItemGroup> @@ -66,6 +72,10 @@ <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> <Name>GodotTools.BuildLogger</Name> </ProjectReference> + <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj"> + <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project> + <Name>GodotTools.IdeConnection</Name> + </ProjectReference> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> <Name>GodotTools.ProjectEditor</Name> @@ -75,8 +85,5 @@ <Name>GodotTools.Core</Name> </ProjectReference> </ItemGroup> - <ItemGroup> - <Folder Include="Editor" /> - </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs new file mode 100644 index 0000000000..9e24138143 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; +using Godot; +using GodotTools.IdeConnection; +using GodotTools.Internals; + +namespace GodotTools.Ides +{ + public class GodotIdeManager : Node, ISerializationListener + { + public GodotIdeServer GodotIdeServer { get; private set; } + + private MonoDevelop.Instance monoDevelInstance; + private MonoDevelop.Instance vsForMacInstance; + + private GodotIdeServer GetRunningServer() + { + if (GodotIdeServer != null && !GodotIdeServer.IsDisposed) + return GodotIdeServer; + StartServer(); + return GodotIdeServer; + } + + public override void _Ready() + { + StartServer(); + } + + public void OnBeforeSerialize() + { + GodotIdeServer?.Dispose(); + } + + public void OnAfterDeserialize() + { + StartServer(); + } + + private ILogger logger; + + protected ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private void StartServer() + { + GodotIdeServer?.Dispose(); + GodotIdeServer = new GodotIdeServer(LaunchIde, + OS.GetExecutablePath(), + ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir)); + + GodotIdeServer.Logger = Logger; + + GodotIdeServer.StartServer(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + GodotIdeServer?.Dispose(); + } + + private void LaunchIde() + { + var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface() + .GetEditorSettings().GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditorId.None: + case ExternalEditorId.VisualStudio: + case ExternalEditorId.VsCode: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) + { + if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac) + { + vsForMacInstance = vsForMacInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); + return vsForMacInstance; + } + + monoDevelInstance = monoDevelInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); + return monoDevelInstance; + } + + try + { + var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath); + + if (!instance.IsRunning) + instance.Execute(); + } + catch (FileNotFoundException) + { + string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + GD.PushError($"Cannot find code editor: {editorName}"); + } + + break; + } + + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void WriteMessage(string id, params string[] arguments) + { + GetRunningServer().WriteMessage(new Message(id, arguments)); + } + + public void SendOpenFile(string file) + { + WriteMessage("OpenFile", file); + } + + public void SendOpenFile(string file, int line) + { + WriteMessage("OpenFile", file, line.ToString()); + } + + public void SendOpenFile(string file, int line, int column) + { + WriteMessage("OpenFile", file, line.ToString(), column.ToString()); + } + + private class GodotLogger : ILogger + { + public void LogDebug(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogInfo(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogWarning(string message) + { + GD.PushWarning(message); + } + + public void LogError(string message) + { + GD.PushError(message); + } + + public void LogError(string message, Exception e) + { + GD.PushError(message + "\n" + e); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs new file mode 100644 index 0000000000..309b917c71 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeConnection; +using GodotTools.Internals; +using GodotTools.Utils; +using Directory = System.IO.Directory; +using File = System.IO.File; +using Thread = System.Threading.Thread; + +namespace GodotTools.Ides +{ + public class GodotIdeServer : GodotIdeBase + { + private readonly TcpListener listener; + private readonly FileStream metaFile; + private readonly Action launchIdeAction; + private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>(); + + private async Task<bool> AwaitClientConnected() + { + return await clientConnectedAwaiter.Reset(); + } + + public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir) + : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + this.launchIdeAction = launchIdeAction; + + // Make sure the directory exists + Directory.CreateDirectory(projectMetadataDir); + + // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... + const FileShare metaFileShare = FileShare.ReadWrite; + + metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + + listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + listener.Start(); + + int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + { + metaFileWriter.WriteLine(port); + metaFileWriter.WriteLine(editorExecutablePath); + } + + StartServer(); + } + + public void StartServer() + { + var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"}; + serverThread.Start(); + } + + private void RunServerThread() + { + SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext); + + try + { + while (!IsDisposed) + { + TcpClient tcpClient = listener.AcceptTcpClient(); + + Logger.LogInfo("Connection open with Ide Client"); + + lock (ConnectionLock) + { + Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage); + Connection.Logger = Logger; + } + + Connected += () => clientConnectedAwaiter.SetResult(true); + + Connection.Start(); + } + } + catch (Exception e) + { + if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted)) + throw; + } + } + + public async void WriteMessage(Message message) + { + async Task LaunchIde() + { + if (IsConnected) + return; + + launchIdeAction(); + await Task.WhenAny(Task.Delay(10000), AwaitClientConnected()); + } + + await LaunchIde(); + + if (!IsConnected) + { + Logger.LogError("Cannot write message: Godot Ide Server not connected"); + return; + } + + Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + listener?.Stop(); + + metaFile?.Dispose(); + + File.Delete(MetaFilePath); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["Play"] = args => + { + switch (args.Length) + { + case 0: + Play(); + return; + case 2: + Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1])); + return; + default: + throw new ArgumentException(); + } + }, + ["ReloadScripts"] = args => ReloadScripts() + }; + } + + private void DispatchToMainThread(Action action) + { + var d = new SendOrPostCallback(state => action()); + Godot.Dispatcher.SynchronizationContext.Post(d, null); + } + + private void Play() + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void Play(string debuggerHost, int debuggerPort) + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void ReloadScripts() + { + DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); + } + + public PlayRequest? CurrentPlayRequest { get; private set; } + + public struct PlayRequest + { + public bool HasDebugger { get; } + public string DebuggerHost { get; } + public int DebuggerPort { get; } + + public PlayRequest(string debuggerHost, int debuggerPort) + { + HasDebugger = true; + DebuggerHost = debuggerHost; + DebuggerPort = debuggerPort; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs new file mode 100644 index 0000000000..1dfc91d6d1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs @@ -0,0 +1,8 @@ +namespace GodotTools.Ides.MonoDevelop +{ + public enum EditorId + { + MonoDevelop = 0, + VisualStudioForMac = 1 + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 0c8d86e799..1fdccf5bbd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -1,26 +1,22 @@ -using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; using GodotTools.Internals; +using GodotTools.Utils; -namespace GodotTools +namespace GodotTools.Ides.MonoDevelop { - public class MonoDevelopInstance + public class Instance { - public enum EditorId - { - MonoDevelop = 0, - VisualStudioForMac = 1 - } - private readonly string solutionFile; private readonly EditorId editorId; private Process process; - public void Execute(params string[] files) + public bool IsRunning => process != null && !process.HasExited; + + public void Execute() { bool newWindow = process == null || process.HasExited; @@ -28,9 +24,9 @@ namespace GodotTools string command; - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { - string bundleId = CodeEditorBundleIds[editorId]; + string bundleId = BundleIds[editorId]; if (Internal.IsOsxAppBundleInstalled(bundleId)) { @@ -47,12 +43,12 @@ namespace GodotTools } else { - command = CodeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } } else { - command = CodeEditorPaths[editorId]; + command = OS.PathWhich(ExecutableNames[editorId]); } args.Add("--ipc-tcp"); @@ -60,15 +56,8 @@ namespace GodotTools if (newWindow) args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); - foreach (var file in files) - { - int semicolonIndex = file.IndexOf(';'); - - string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex); - string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex); - - args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); - } + if (command == null) + throw new FileNotFoundException(); if (newWindow) { @@ -76,7 +65,7 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true }); } else @@ -85,42 +74,42 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true })?.Dispose(); } } - public MonoDevelopInstance(string solutionFile, EditorId editorId) + public Instance(string solutionFile, EditorId editorId) { - if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX()) + if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX()) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); this.solutionFile = solutionFile; this.editorId = editorId; } - private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths; - private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds; + private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; + private static readonly IReadOnlyDictionary<EditorId, string> BundleIds; - static MonoDevelopInstance() + static Instance() { - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { - CodeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // Rely on PATH {EditorId.MonoDevelop, "monodevelop"}, {EditorId.VisualStudioForMac, "VisualStudio"} }; - CodeEditorBundleIds = new Dictionary<EditorId, string> + BundleIds = new Dictionary<EditorId, string> { // TODO EditorId.MonoDevelop {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } - else if (Utils.OS.IsWindows()) + else if (OS.IsWindows()) { - CodeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // XamarinStudio is no longer a thing, and the latest version is quite old // MonoDevelop is available from source only on Windows. The recommendation @@ -129,9 +118,9 @@ namespace GodotTools {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } - else if (Utils.OS.IsUnix()) + else if (OS.IsUnix()) { - CodeEditorPaths = new Dictionary<EditorId, string> + ExecutableNames = new Dictionary<EditorId, string> { // Rely on PATH {EditorId.MonoDevelop, "monodevelop"} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 9526dd3c6f..7783576910 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -46,6 +46,12 @@ namespace GodotTools.Internals public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + public static void EditorRunPlay() => internal_EditorRunPlay(); + + public static void EditorRunStop() => internal_EditorRunStop(); + + public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + // Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] @@ -95,5 +101,14 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoWindowsInstallRoot(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunPlay(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunStop(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebugger_ReloadScripts(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index 3ae6c10bbf..e3c2c822a5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace GodotTools.Utils { @@ -10,11 +11,19 @@ namespace GodotTools.Utils { foreach (T elem in enumerable) { - if (predicate(elem) != null) - return elem; + T result = predicate(elem); + if (result != null) + return result; } return orElse; } + + public static IEnumerable<string> EnumerateLines(this TextReader textReader) + { + string line; + while ((line = textReader.ReadLine()) != null) + yield return line; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs new file mode 100644 index 0000000000..700b786752 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public sealed class NotifyAwaiter<T> : INotifyCompletion + { + private Action continuation; + private Exception exception; + private T result; + + public bool IsCompleted { get; private set; } + + public T GetResult() + { + if (exception != null) + throw exception; + return result; + } + + public void OnCompleted(Action continuation) + { + if (this.continuation != null) + throw new InvalidOperationException("This awaiter has already been listened"); + this.continuation = continuation; + } + + public void SetResult(T result) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.result = result; + + continuation?.Invoke(); + } + + public void SetException(Exception exception) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.exception = exception; + + continuation?.Invoke(); + } + + public NotifyAwaiter<T> Reset() + { + continuation = null; + exception = null; + result = default(T); + IsCompleted = false; + return this; + } + + public NotifyAwaiter<T> GetAwaiter() + { + return this; + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 45037bf637..1888bb3cb9 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -279,7 +279,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf Vector<String> link_target_parts = link_target.split("."); if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) { - ERR_PRINTS("Invalid reference format: " + tag); + ERR_PRINTS("Invalid reference format: '" + tag + "'."); xml_output.append("<c>"); xml_output.append(tag); @@ -375,7 +375,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve enum reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve enum reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -424,7 +424,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_iconst->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve global constant reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve global constant reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -464,7 +464,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_iconst->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve constant reference in documentation: " + link_target); + ERR_PRINTS("Cannot resolve constant reference in documentation: '" + link_target + "'."); xml_output.append("<c>"); xml_output.append(link_target); @@ -534,7 +534,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(target_itype->proxy_name); xml_output.append("\"/>"); } else { - ERR_PRINTS("Cannot resolve type reference in documentation: " + tag); + ERR_PRINTS("Cannot resolve type reference in documentation: '" + tag + "'."); xml_output.append("<c>"); xml_output.append(tag); @@ -812,7 +812,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - _log("Declaring global enum `%s` inside static class `%s`\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); + _log("Declaring global enum '%s' inside static class '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); p_output.append("\n" INDENT1 "public static partial class "); p_output.append(enum_class_name); @@ -1083,7 +1083,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { proj_err = generate_cs_core_project(core_proj_dir, core_compile_items); if (proj_err != OK) { - ERR_PRINT("Generation of the Core API C# project failed"); + ERR_PRINT("Generation of the Core API C# project failed."); return proj_err; } @@ -1094,7 +1094,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items); if (proj_err != OK) { - ERR_PRINT("Generation of the Editor API C# project failed"); + ERR_PRINT("Generation of the Editor API C# project failed."); return proj_err; } @@ -1112,7 +1112,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { // FIXME: There are some members that hide other inherited members. // - In the case of both members being the same kind, the new one must be declared -// explicitly as `new` to avoid the warning (and we must print a message about it). +// explicitly as 'new' to avoid the warning (and we must print a message about it). // - In the case of both members being of a different kind, then the new one must // be renamed to avoid the name collision (and we must print a warning about it). // - Csc warning e.g.: @@ -1186,7 +1186,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(obj_types[itype.base_name].proxy_name); output.append("\n"); } else { - ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class " + itype.name); + ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'."); return ERR_INVALID_DATA; } } @@ -1273,11 +1273,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) { const PropertyInterface &iprop = E->get(); Error prop_err = _generate_cs_property(itype, iprop, output); - if (prop_err != OK) { - ERR_EXPLAIN("Failed to generate property '" + iprop.cname.operator String() + - "' for class '" + itype.name + "'"); - ERR_FAIL_V(prop_err); - } + ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err, + "Failed to generate property '" + iprop.cname.operator String() + + "' for class '" + itype.name + "'."); } } @@ -1340,10 +1338,8 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output); - if (method_err != OK) { - ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'"); - ERR_FAIL_V(method_err); - } + ERR_FAIL_COND_V_MSG(method_err != OK, method_err, + "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } if (itype.is_singleton) { @@ -1626,7 +1622,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.is_deprecated) { if (p_imethod.deprecation_message.empty()) - WARN_PRINTS("An empty deprecation message is discouraged. Method: " + p_imethod.proxy_name); + WARN_PRINTS("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); p_output.append(MEMBER_BEGIN "[Obsolete(\""); p_output.append(p_imethod.deprecation_message); @@ -1708,8 +1704,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf Error BindingsGenerator::generate_glue(const String &p_output_dir) { bool dir_exists = DirAccess::exists(p_output_dir); - ERR_EXPLAIN("The output directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist."); StringBuilder output; @@ -1742,10 +1737,8 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { const MethodInterface &imethod = E->get(); Error method_err = _generate_glue_method(itype, imethod, output); - if (method_err != OK) { - ERR_EXPLAIN("Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'"); - ERR_FAIL_V(method_err); - } + ERR_FAIL_COND_V_MSG(method_err != OK, method_err, + "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } if (itype.is_singleton) { @@ -1879,8 +1872,7 @@ Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); - ERR_EXPLAIN("Cannot open file: " + p_path); - ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + ERR_FAIL_COND_V_MSG(!file, ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); file->store_string(p_content.as_string()); file->close(); @@ -2091,7 +2083,7 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol if (found) return found; - ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_typeref.cname.operator String()); + ERR_PRINTS(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'."); const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_typeref.cname); @@ -2175,13 +2167,13 @@ void BindingsGenerator::_populate_object_type_interfaces() { } if (!ClassDB::is_class_exposed(type_cname)) { - _log("Ignoring type `%s` because it's not exposed\n", String(type_cname).utf8().get_data()); + _log("Ignoring type '%s' because it's not exposed\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } if (!ClassDB::is_class_enabled(type_cname)) { - _log("Ignoring type `%s` because it's not enabled\n", String(type_cname).utf8().get_data()); + _log("Ignoring type '%s' because it's not enabled\n", String(type_cname).utf8().get_data()); class_list.pop_front(); continue; } @@ -2240,7 +2232,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Prevent the property and its enclosing type from sharing the same name if (iprop.proxy_name == itype.proxy_name) { - _log("Name of property `%s` is ambiguous with the name of its enclosing class `%s`. Renaming property to `%s_`\n", + _log("Name of property '%s' is ambiguous with the name of its enclosing class '%s'. Renaming property to '%s_'\n", iprop.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), iprop.proxy_name.utf8().get_data()); iprop.proxy_name += "_"; @@ -2298,28 +2290,26 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.is_vararg = m && m->is_vararg(); if (!m && !imethod.is_virtual) { - if (virtual_method_list.find(method_info)) { - // A virtual method without the virtual flag. This is a special case. - - // There is no method bind, so let's fallback to Godot's object.Call(string, params) - imethod.requires_object_call = true; - - // The method Object.free is registered as a virtual method, but without the virtual flag. - // This is because this method is not supposed to be overridden, but called. - // We assume the return type is void. - imethod.return_type.cname = name_cache.type_void; - - // Actually, more methods like this may be added in the future, - // which could actually will return something different. - // Let's put this to notify us if that ever happens. - if (itype.cname != name_cache.type_Object || imethod.name != "free") { - ERR_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" - "We only expected Object.free, but found " + - itype.name + "." + imethod.name); - } - } else { - ERR_EXPLAIN("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); - ERR_FAIL(); + ERR_FAIL_COND_MSG(!virtual_method_list.find(method_info), + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + + // A virtual method without the virtual flag. This is a special case. + + // There is no method bind, so let's fallback to Godot's object.Call(string, params) + imethod.requires_object_call = true; + + // The method Object.free is registered as a virtual method, but without the virtual flag. + // This is because this method is not supposed to be overridden, but called. + // We assume the return type is void. + imethod.return_type.cname = name_cache.type_void; + + // Actually, more methods like this may be added in the future, + // which could actually will return something different. + // Let's put this to notify us if that ever happens. + if (itype.cname != name_cache.type_Object || imethod.name != "free") { + ERR_PRINTS("Notification: New unexpected virtual non-overridable method found." + " We only expected Object.free, but found '" + + itype.name + "." + imethod.name + "'."); } } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { imethod.return_type.cname = return_info.class_name; @@ -2328,8 +2318,8 @@ void BindingsGenerator::_populate_object_type_interfaces() { imethod.return_type.cname = return_info.class_name; if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) { /* clang-format off */ - ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "." - " Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name); + ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'."); /* clang-format on */ ERR_FAIL(); } @@ -2394,7 +2384,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { // Prevent the method and its enclosing type from sharing the same name if (imethod.proxy_name == itype.proxy_name) { - _log("Name of method `%s` is ambiguous with the name of its enclosing class `%s`. Renaming method to `%s_`\n", + _log("Name of method '%s' is ambiguous with the name of its enclosing class '%s'. Renaming method to '%s_'\n", imethod.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), imethod.proxy_name.utf8().get_data()); imethod.proxy_name += "_"; @@ -2880,8 +2870,7 @@ void BindingsGenerator::_populate_global_constants() { if (global_constants_count > 0) { Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope"); - ERR_EXPLAIN("Could not find `@GlobalScope` in DocData"); - CRASH_COND(!match); + CRASH_COND_MSG(!match, "Could not find '@GlobalScope' in DocData."); const DocData::ClassDoc &global_scope_doc = match->value(); @@ -2935,7 +2924,7 @@ void BindingsGenerator::_populate_global_constants() { // HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'. if (ienum.cname == name_cache.enum_Error) { if (prefix_length > 0) { // Just in case it ever changes - ERR_PRINTS("Prefix for enum 'Error' is not empty"); + ERR_PRINTS("Prefix for enum '" _STR(Error) "' is not empty."); } prefix_length = 1; // 'ERR_' @@ -3024,7 +3013,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) glue_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)"); + ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue')."); } --options_left; @@ -3035,7 +3024,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) cs_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_cs_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_cs_glue_option + ": No output directory specified."); } --options_left; @@ -3046,7 +3035,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) cpp_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified."); } --options_left; @@ -3061,20 +3050,20 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) if (glue_dir_path.length()) { if (bindings_generator.generate_glue(glue_dir_path) != OK) - ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue"); + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue."); if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK) - ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API"); + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API."); } if (cs_dir_path.length()) { if (bindings_generator.generate_cs_api(cs_dir_path) != OK) - ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API"); + ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API."); } if (cpp_dir_path.length()) { if (bindings_generator.generate_glue(cpp_dir_path) != OK) - ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue"); + ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue."); } // Exit once done diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 8be51a6c55..6f0c297575 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -147,7 +147,7 @@ class BindingsGenerator { bool requires_object_call; /** - * Determines if the method visibility is `internal` (visible only to files in the same assembly). + * Determines if the method visibility is 'internal' (visible only to files in the same assembly). * Currently, we only use this for methods that are not meant to be exposed, * but are required by properties as getters or setters. * Methods that are not meant to be exposed are those that begin with underscore and are not virtual. diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index d88b08c646..0e6c58c9d7 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -81,16 +81,14 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr _GDMONO_SCOPE_DOMAIN_(temp_domain); - GDMonoAssembly *tools_project_editor_assembly = NULL; + GDMonoAssembly *tools_project_editor_asm = NULL; - if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) { - ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'"); - ERR_FAIL_V(false); - } + bool assembly_loaded = GDMono::get_singleton()->load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_asm); + ERR_FAIL_COND_V_MSG(!assembly_loaded, false, "Failed to load assembly: '" TOOLS_PROJECT_EDITOR_ASM_NAME "'."); return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, p_editor_proj_dir, p_editor_compile_items, - tools_project_editor_assembly); + tools_project_editor_asm); } } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 0014aaca70..5a84d9e3b8 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -271,7 +271,7 @@ MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { #ifdef OSX_ENABLED String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); - return (MonoBoolean)osx_is_app_bundle_installed; + return (MonoBoolean)osx_is_app_bundle_installed(bundle_id); #else (void)p_bundle_id; // UNUSED return (MonoBoolean) false; @@ -350,6 +350,21 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { #endif } +void godot_icall_Internal_EditorRunPlay() { + EditorNode::get_singleton()->run_play(); +} + +void godot_icall_Internal_EditorRunStop() { + EditorNode::get_singleton()->run_stop(); +} + +void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { + ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + if (sed) { + sed->reload_scripts(); + } +} + MonoString *godot_icall_Utils_OS_GetPlatformName() { String os_name = OS::get_singleton()->get_name(); return GDMonoMarshal::mono_string_from_godot(os_name); @@ -415,6 +430,9 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); // Globals mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 020bb70a08..80a7335b1d 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -85,18 +85,12 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co } } - if (!ref_assembly) { - ERR_EXPLAIN("Cannot load assembly (refonly): " + ref_name); - ERR_FAIL_V(ERR_CANT_RESOLVE); - } + ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); r_dependencies[ref_name] = ref_assembly->get_path(); Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); - if (err != OK) { - ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); - ERR_FAIL_V(err); - } + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); } return OK; @@ -113,8 +107,7 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); - ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name); - ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE); + ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'."); Vector<String> search_dirs; GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index dfb652a7aa..dcb0ca5a80 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -162,8 +162,8 @@ ScriptClassParser::Token ScriptClassParser::get_token() { error = true; return TK_ERROR; } else if (code[idx] == begin_str) { - if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"` - idx += 2; // skip next `"` as well + if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"' + idx += 2; // skip next '"' as well continue; } @@ -590,7 +590,7 @@ Error ScriptClassParser::parse(const String &p_code) { name = String(value); } else if (tk == TK_CURLY_BRACKET_OPEN) { if (name.empty()) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword `struct`, found " + get_token_name(TK_CURLY_BRACKET_OPEN); + error_str = "Expected " + get_token_name(TK_IDENTIFIER) + " after keyword 'struct', found " + get_token_name(TK_CURLY_BRACKET_OPEN); error = true; return ERR_PARSE_ERROR; } @@ -657,12 +657,12 @@ Error ScriptClassParser::parse_file(const String &p_filepath) { String source; Error ferr = read_all_file_utf8(p_filepath, source); - if (ferr != OK) { - if (ferr == ERR_INVALID_DATA) { - ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); - } - ERR_FAIL_V(ferr); - } + + ERR_FAIL_COND_V_MSG(ferr != OK, ferr, + ferr == ERR_INVALID_DATA ? + "File '" + p_filepath + "' 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_filepath + "'."); return parse(source); } diff --git a/modules/mono/glue/Managed/Files/AABB.cs b/modules/mono/glue/Managed/Files/AABB.cs index a2ebbc0736..98a73382f4 100644 --- a/modules/mono/glue/Managed/Files/AABB.cs +++ b/modules/mono/glue/Managed/Files/AABB.cs @@ -5,6 +5,7 @@ // file: core/variant_call.cpp // commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685 using System; +using System.Runtime.InteropServices; #if REAL_T_IS_DOUBLE using real_t = System.Double; #else @@ -13,6 +14,8 @@ using real_t = System.Single; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct AABB : IEquatable<AABB> { private Vector3 _position; diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index 9cc31a0557..0eb76e9c63 100644 --- a/modules/mono/glue/Managed/Files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -8,43 +8,10 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Basis : IEquatable<Basis> { - private static readonly Basis identity = new Basis - ( - 1f, 0f, 0f, - 0f, 1f, 0f, - 0f, 0f, 1f - ); - - private static readonly Basis[] orthoBases = { - new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), - new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), - new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), - new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), - new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), - new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), - new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), - new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), - new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), - new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), - new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), - new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), - new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), - new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) - }; - // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally. /// <summary> @@ -63,7 +30,6 @@ namespace Godot /// </summary> public Vector3 y { - get => Column1; set => Column1 = value; } @@ -74,7 +40,6 @@ namespace Godot /// </summary> public Vector3 z { - get => Column2; set => Column2 = value; } @@ -114,8 +79,6 @@ namespace Godot } } - public static Basis Identity => identity; - public Vector3 Scale { get @@ -225,7 +188,7 @@ namespace Godot return orthonormalizedBasis.Quat(); } - internal void SetQuantScale(Quat quat, Vector3 scale) + internal void SetQuatScale(Quat quat, Vector3 scale) { SetDiagonal(scale); Rotate(quat); @@ -241,7 +204,6 @@ namespace Godot Row0 = new Vector3(diagonal.x, 0, 0); Row1 = new Vector3(0, diagonal.y, 0); Row2 = new Vector3(0, 0, diagonal.z); - } public real_t Determinant() @@ -361,7 +323,7 @@ namespace Godot for (int i = 0; i < 24; i++) { - if (orthoBases[i] == orth) + if (orth == _orthoBases[i]) return i; } @@ -531,6 +493,43 @@ namespace Godot } } + private static readonly Basis[] _orthoBases = { + new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), + new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), + new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), + new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), + new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), + new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), + new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), + new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), + new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), + new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), + new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), + new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), + new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), + new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) + }; + + private static readonly Basis _identity = new Basis(1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipX = new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); + private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); + + public static Basis Identity { get { return _identity; } } + public static Basis FlipX { get { return _flipX; } } + public static Basis FlipY { get { return _flipY; } } + public static Basis FlipZ { get { return _flipZ; } } + public Basis(Quat quat) { real_t s = 2.0f / quat.LengthSquared; diff --git a/modules/mono/glue/Managed/Files/Color.cs b/modules/mono/glue/Managed/Files/Color.cs index 84ff19fc54..3a52a1a13b 100644 --- a/modules/mono/glue/Managed/Files/Color.cs +++ b/modules/mono/glue/Managed/Files/Color.cs @@ -1,7 +1,10 @@ using System; +using System.Runtime.InteropServices; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct Color : IEquatable<Color> { public float r; @@ -13,7 +16,11 @@ namespace Godot { get { - return (int)(r * 255.0f); + return (int)Math.Round(r * 255.0f); + } + set + { + r = value / 255.0f; } } @@ -21,7 +28,11 @@ namespace Godot { get { - return (int)(g * 255.0f); + return (int)Math.Round(g * 255.0f); + } + set + { + g = value / 255.0f; } } @@ -29,7 +40,11 @@ namespace Godot { get { - return (int)(b * 255.0f); + return (int)Math.Round(b * 255.0f); + } + set + { + b = value / 255.0f; } } @@ -37,7 +52,11 @@ namespace Godot { get { - return (int)(a * 255.0f); + return (int)Math.Round(a * 255.0f); + } + set + { + a = value / 255.0f; } } @@ -71,7 +90,7 @@ namespace Godot } set { - this = FromHsv(value, s, v); + this = FromHsv(value, s, v, a); } } @@ -88,7 +107,7 @@ namespace Godot } set { - this = FromHsv(h, value, v); + this = FromHsv(h, value, v, a); } } @@ -100,7 +119,7 @@ namespace Godot } set { - this = FromHsv(h, s, value); + this = FromHsv(h, s, value, a); } } @@ -163,12 +182,12 @@ namespace Godot } } - public static void ToHsv(Color color, out float hue, out float saturation, out float value) + public void ToHsv(out float hue, out float saturation, out float value) { - int max = Mathf.Max(color.r8, Mathf.Max(color.g8, color.b8)); - int min = Mathf.Min(color.r8, Mathf.Min(color.g8, color.b8)); + float max = (float)Mathf.Max(r, Mathf.Max(g, b)); + float min = (float)Mathf.Min(r, Mathf.Min(g, b)); - int delta = max - min; + float delta = max - min; if (delta == 0) { @@ -176,12 +195,12 @@ namespace Godot } else { - if (color.r == max) - hue = (color.g - color.b) / delta; // Between yellow & magenta - else if (color.g == max) - hue = 2 + (color.b - color.r) / delta; // Between cyan & yellow + if (r == max) + hue = (g - b) / delta; // Between yellow & magenta + else if (g == max) + hue = 2 + (b - r) / delta; // Between cyan & yellow else - hue = 4 + (color.r - color.g) / delta; // Between magenta & cyan + hue = 4 + (r - g) / delta; // Between magenta & cyan hue /= 6.0f; @@ -190,7 +209,7 @@ namespace Godot } saturation = max == 0 ? 0 : 1f - 1f * min / max; - value = max / 255f; + value = max; } public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f) @@ -254,7 +273,8 @@ namespace Godot return new Color( (r + 0.5f) % 1.0f, (g + 0.5f) % 1.0f, - (b + 0.5f) % 1.0f + (b + 0.5f) % 1.0f, + a ); } @@ -272,7 +292,8 @@ namespace Godot return new Color( 1.0f - r, 1.0f - g, - 1.0f - b + 1.0f - b, + a ); } @@ -375,7 +396,7 @@ namespace Godot return c; } - public string ToHtml(bool include_alpha = true) + public string ToHtml(bool includeAlpha = true) { var txt = string.Empty; @@ -383,7 +404,7 @@ namespace Godot txt += ToHex32(g); txt += ToHex32(b); - if (include_alpha) + if (includeAlpha) txt = ToHex32(a) + txt; return txt; @@ -465,13 +486,13 @@ namespace Godot for (int i = 0; i < 2; i++) { - char[] c = { (char)0, (char)0 }; + char c; int lv = v & 0xF; if (lv < 10) - c[0] = (char)('0' + lv); + c = (char)('0' + lv); else - c[0] = (char)('a' + lv - 10); + c = (char)('a' + lv - 10); v >>= 4; ret = c + ret; @@ -490,12 +511,17 @@ namespace Godot bool alpha; - if (color.Length == 8) - alpha = true; - else if (color.Length == 6) - alpha = false; - else - return false; + switch (color.Length) + { + case 8: + alpha = true; + break; + case 6: + alpha = false; + break; + default: + return false; + } if (alpha) { diff --git a/modules/mono/glue/Managed/Files/Colors.cs b/modules/mono/glue/Managed/Files/Colors.cs index bc2a1a3bd7..f41f5e9fc8 100644 --- a/modules/mono/glue/Managed/Files/Colors.cs +++ b/modules/mono/glue/Managed/Files/Colors.cs @@ -141,6 +141,7 @@ namespace Godot {"teal", new Color(0.00f, 0.50f, 0.50f)}, {"thistle", new Color(0.85f, 0.75f, 0.85f)}, {"tomato", new Color(1.00f, 0.39f, 0.28f)}, + {"transparent", new Color(1.00f, 1.00f, 1.00f, 0.00f)}, {"turquoise", new Color(0.25f, 0.88f, 0.82f)}, {"violet", new Color(0.93f, 0.51f, 0.93f)}, {"webgreen", new Color(0.00f, 0.50f, 0.00f)}, @@ -187,7 +188,7 @@ namespace Godot public static Color DarkOrchid { get { return namedColors["darkorchid"]; } } public static Color DarkRed { get { return namedColors["darkred"]; } } public static Color DarkSalmon { get { return namedColors["darksalmon"]; } } - public static Color DarkSeagreen { get { return namedColors["darkseagreen"]; } } + public static Color DarkSeaGreen { get { return namedColors["darkseagreen"]; } } public static Color DarkSlateBlue { get { return namedColors["darkslateblue"]; } } public static Color DarkSlateGray { get { return namedColors["darkslategray"]; } } public static Color DarkTurquoise { get { return namedColors["darkturquoise"]; } } @@ -288,6 +289,7 @@ namespace Godot public static Color Teal { get { return namedColors["teal"]; } } public static Color Thistle { get { return namedColors["thistle"]; } } public static Color Tomato { get { return namedColors["tomato"]; } } + public static Color Transparent { get { return namedColors["transparent"]; } } public static Color Turquoise { get { return namedColors["turquoise"]; } } public static Color Violet { get { return namedColors["violet"]; } } public static Color WebGreen { get { return namedColors["webgreen"]; } } diff --git a/modules/mono/glue/Managed/Files/Dispatcher.cs b/modules/mono/glue/Managed/Files/Dispatcher.cs new file mode 100644 index 0000000000..072e0f20ff --- /dev/null +++ b/modules/mono/glue/Managed/Files/Dispatcher.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; + +namespace Godot +{ + public static class Dispatcher + { + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler(); + + public static GodotSynchronizationContext SynchronizationContext => + godot_icall_DefaultGodotTaskScheduler().Context; + } +} diff --git a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs index e727781d63..4b5e3f8761 100644 --- a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs +++ b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs @@ -6,17 +6,16 @@ namespace Godot { public class GodotSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); public override void Post(SendOrPostCallback d, object state) { - queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); + _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); } public void ExecutePendingContinuations() { - KeyValuePair<SendOrPostCallback, object> workItem; - while (queue.TryTake(out workItem)) + while (_queue.TryTake(out var workItem)) { workItem.Key(workItem.Value); } diff --git a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs index 9a40fef5a9..8eaeea50dc 100644 --- a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs +++ b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs @@ -8,7 +8,7 @@ namespace Godot { public class GodotTaskScheduler : TaskScheduler { - private GodotSynchronizationContext Context { get; set; } + internal GodotSynchronizationContext Context { get; } private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); public GodotTaskScheduler() @@ -28,14 +28,10 @@ namespace Godot protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { if (SynchronizationContext.Current != Context) - { return false; - } if (taskWasPreviouslyQueued) - { TryDequeue(task); - } return TryExecuteTask(task); } @@ -52,7 +48,8 @@ namespace Godot { lock (_tasks) { - return _tasks.ToArray(); + foreach (Task task in _tasks) + yield return task; } } diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs index 6c1a51fcf9..ce34cd6a99 100644 --- a/modules/mono/glue/Managed/Files/Mathf.cs +++ b/modules/mono/glue/Managed/Files/Mathf.cs @@ -158,6 +158,11 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. real_t tolerance = Epsilon * Abs(a); if (tolerance < Epsilon) { tolerance = Epsilon; @@ -336,14 +341,14 @@ namespace Godot public static int Wrap(int value, int min, int max) { - int rng = max - min; - return rng != 0 ? min + ((value - min) % rng + rng) % rng : min; + int range = max - min; + return range == 0 ? min : min + ((value - min) % range + range) % range; } public static real_t Wrap(real_t value, real_t min, real_t max) { - real_t rng = max - min; - return !IsEqualApprox(rng, default(real_t)) ? min + ((value - min) % rng + rng) % rng : min; + real_t range = max - min; + return IsZeroApprox(range) ? min : min + ((value - min) % range + range) % range; } } } diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs index b96f01bc2e..6cffc7f01d 100644 --- a/modules/mono/glue/Managed/Files/MathfEx.cs +++ b/modules/mono/glue/Managed/Files/MathfEx.cs @@ -48,7 +48,12 @@ namespace Godot public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance) { + // Check for exact equality first, required to handle "infinity" values. + if (a == b) { + return true; + } + // Then check for approximate equality. return Abs(a - b) < tolerance; } } -}
\ No newline at end of file +} diff --git a/modules/mono/glue/Managed/Files/NodePath.cs b/modules/mono/glue/Managed/Files/NodePath.cs index 94a4ed1de9..4de4e1e6a9 100644 --- a/modules/mono/glue/Managed/Files/NodePath.cs +++ b/modules/mono/glue/Managed/Files/NodePath.cs @@ -55,7 +55,7 @@ namespace Godot get { return ptr; } } - public NodePath() : this(string.Empty) { } + public NodePath() : this(string.Empty) {} public NodePath(string path) { diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index e16d4315be..a13161d2e6 100644 --- a/modules/mono/glue/Managed/Files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; #if REAL_T_IS_DOUBLE using real_t = System.Double; #else @@ -7,6 +8,8 @@ using real_t = System.Single; namespace Godot { + [Serializable] + [StructLayout(LayoutKind.Sequential)] public struct Plane : IEquatable<Plane> { private Vector3 _normal; diff --git a/modules/mono/glue/Managed/Files/Quat.cs b/modules/mono/glue/Managed/Files/Quat.cs index 0d4349084a..845c7c730e 100644 --- a/modules/mono/glue/Managed/Files/Quat.cs +++ b/modules/mono/glue/Managed/Files/Quat.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Quat : IEquatable<Quat> { @@ -95,6 +96,7 @@ namespace Godot return this / Length; } + [Obsolete("Set is deprecated. Use the Quat(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y, real_t z, real_t w) { this.x = x; @@ -103,16 +105,19 @@ namespace Godot this.w = w; } + [Obsolete("Set is deprecated. Use the Quat(" + nameof(Quat) + ") constructor instead.", error: true)] public void Set(Quat q) { this = q; } + [Obsolete("SetAxisAngle is deprecated. Use the Quat(" + nameof(Vector3) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void SetAxisAngle(Vector3 axis, real_t angle) { this = new Quat(axis, angle); } + [Obsolete("SetEuler is deprecated. Use the Quat(" + nameof(Vector3) + ") constructor instead.", error: true)] public void SetEuler(Vector3 eulerYXZ) { this = new Quat(eulerYXZ); @@ -229,9 +234,9 @@ namespace Godot public Quat(Vector3 eulerYXZ) { - real_t half_a1 = eulerYXZ.y * (real_t)0.5; - real_t half_a2 = eulerYXZ.x * (real_t)0.5; - real_t half_a3 = eulerYXZ.z * (real_t)0.5; + real_t half_a1 = eulerYXZ.y * 0.5f; + real_t half_a2 = eulerYXZ.x * 0.5f; + real_t half_a3 = eulerYXZ.z * 0.5f; // R = Y(a1).X(a2).Z(a3) convention for Euler angles. // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) @@ -246,7 +251,7 @@ namespace Godot x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3; y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3; - z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3; + z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3; w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3; } diff --git a/modules/mono/glue/Managed/Files/Rect2.cs b/modules/mono/glue/Managed/Files/Rect2.cs index 888f300347..f3dc9d8490 100644 --- a/modules/mono/glue/Managed/Files/Rect2.cs +++ b/modules/mono/glue/Managed/Files/Rect2.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Rect2 : IEquatable<Rect2> { diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs index b43034fbb5..6045c83e95 100644 --- a/modules/mono/glue/Managed/Files/StringExtensions.cs +++ b/modules/mono/glue/Managed/Files/StringExtensions.cs @@ -98,6 +98,66 @@ namespace Godot } // <summary> + // Return the amount of substrings in string. + // </summary> + public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + { + if (what.Length == 0) + { + return 0; + } + + int len = instance.Length; + int slen = what.Length; + + if (len < slen) + { + return 0; + } + + string str; + + if (from >= 0 && to >= 0) + { + if (to == 0) + { + to = len; + } + else if (from >= to) + { + return 0; + } + if (from == 0 && to == len) + { + str = instance; + } + else + { + str = instance.Substring(from, to - from); + } + } + else + { + return 0; + } + + int c = 0; + int idx; + + do + { + idx = str.IndexOf(what, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + if (idx != -1) + { + str = str.Substring(idx + slen); + ++c; + } + } while (idx != -1); + + return c; + } + + // <summary> // Return a copy of the string with special characters escaped using the C language standard. // </summary> public static string CEscape(this string instance) diff --git a/modules/mono/glue/Managed/Files/Transform.cs b/modules/mono/glue/Managed/Files/Transform.cs index bd79144873..cc4d26158d 100644 --- a/modules/mono/glue/Managed/Files/Transform.cs +++ b/modules/mono/glue/Managed/Files/Transform.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform : IEquatable<Transform> { @@ -33,7 +34,7 @@ namespace Godot Vector3 destinationLocation = transform.origin; var interpolated = new Transform(); - interpolated.basis.SetQuantScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c)); + interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c)); interpolated.origin = sourceLocation.LinearInterpolate(destinationLocation, c); return interpolated; diff --git a/modules/mono/glue/Managed/Files/Transform2D.cs b/modules/mono/glue/Managed/Files/Transform2D.cs index 33ff286769..814332dc07 100644 --- a/modules/mono/glue/Managed/Files/Transform2D.cs +++ b/modules/mono/glue/Managed/Files/Transform2D.cs @@ -8,6 +8,7 @@ using real_t = System.Single; namespace Godot { + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform2D : IEquatable<Transform2D> { @@ -98,6 +99,8 @@ namespace Godot return x[columnIndex]; case 1: return y[columnIndex]; + case 2: + return origin[columnIndex]; default: throw new IndexOutOfRangeException(); } @@ -112,6 +115,9 @@ namespace Godot case 1: y[columnIndex] = value; return; + case 2: + origin[columnIndex] = value; + return; default: throw new IndexOutOfRangeException(); } @@ -136,7 +142,7 @@ namespace Godot inv[0] *= new Vector2(detInv, -detInv); inv[1] *= new Vector2(-detInv, detInv); - inv[2] = BasisXform(-inv[2]); + inv[2] = inv.BasisXform(-inv[2]); return inv; } diff --git a/modules/mono/glue/Managed/Files/Vector2.cs b/modules/mono/glue/Managed/Files/Vector2.cs index a7f26283a7..0daa94057e 100644 --- a/modules/mono/glue/Managed/Files/Vector2.cs +++ b/modules/mono/glue/Managed/Files/Vector2.cs @@ -14,9 +14,19 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 2-element structure that can be used to represent positions in 2D space or any other pair of numeric values. + /// </summary> + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector2 : IEquatable<Vector2> { + public enum Axis + { + X = 0, + Y + } + public real_t x; public real_t y; @@ -201,6 +211,22 @@ namespace Godot return v; } + public Vector2 PosMod(real_t mod) + { + Vector2 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + return v; + } + + public Vector2 PosMod(Vector2 modv) + { + Vector2 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + return v; + } + public Vector2 Project(Vector2 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -222,17 +248,27 @@ namespace Godot return new Vector2(Mathf.Round(x), Mathf.Round(y)); } + [Obsolete("Set is deprecated. Use the Vector2(" + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y) { this.x = x; this.y = y; } + [Obsolete("Set is deprecated. Use the Vector2(" + nameof(Vector2) + ") constructor instead.", error: true)] public void Set(Vector2 v) { x = v.x; y = v.y; } + public Vector2 Sign() + { + Vector2 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + return v; + } + public Vector2 Slerp(Vector2 b, real_t t) { real_t theta = AngleTo(b); @@ -262,7 +298,7 @@ namespace Godot private static readonly Vector2 _up = new Vector2(0, -1); private static readonly Vector2 _down = new Vector2(0, 1); - private static readonly Vector2 _right = new Vector2(1, 0); + private static readonly Vector2 _right = new Vector2(1, 0); private static readonly Vector2 _left = new Vector2(-1, 0); public static Vector2 Zero { get { return _zero; } } @@ -343,6 +379,20 @@ namespace Godot return left; } + public static Vector2 operator %(Vector2 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + return vec; + } + + public static Vector2 operator %(Vector2 vec, Vector2 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + return vec; + } + public static bool operator ==(Vector2 left, Vector2 right) { return left.Equals(right); diff --git a/modules/mono/glue/Managed/Files/Vector3.cs b/modules/mono/glue/Managed/Files/Vector3.cs index 16803ae55c..9076dbd3b0 100644 --- a/modules/mono/glue/Managed/Files/Vector3.cs +++ b/modules/mono/glue/Managed/Files/Vector3.cs @@ -14,6 +14,10 @@ using real_t = System.Single; namespace Godot { + /// <summary> + /// 3-element structure that can be used to represent positions in 3D space or any other pair of numeric values. + /// </summary> + [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Vector3 : IEquatable<Vector3> { @@ -224,6 +228,24 @@ namespace Godot ); } + public Vector3 PosMod(real_t mod) + { + Vector3 v; + v.x = Mathf.PosMod(x, mod); + v.y = Mathf.PosMod(y, mod); + v.z = Mathf.PosMod(z, mod); + return v; + } + + public Vector3 PosMod(Vector3 modv) + { + Vector3 v; + v.x = Mathf.PosMod(x, modv.x); + v.y = Mathf.PosMod(y, modv.y); + v.z = Mathf.PosMod(z, modv.z); + return v; + } + public Vector3 Project(Vector3 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); @@ -248,12 +270,14 @@ namespace Godot return new Basis(axis, phi).Xform(this); } + [Obsolete("Set is deprecated. Use the Vector3(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)] public void Set(real_t x, real_t y, real_t z) { this.x = x; this.y = y; this.z = z; } + [Obsolete("Set is deprecated. Use the Vector3(" + nameof(Vector3) + ") constructor instead.", error: true)] public void Set(Vector3 v) { x = v.x; @@ -261,6 +285,15 @@ namespace Godot z = v.z; } + public Vector3 Sign() + { + Vector3 v; + v.x = Mathf.Sign(x); + v.y = Mathf.Sign(y); + v.z = Mathf.Sign(z); + return v; + } + public Vector3 Slerp(Vector3 b, real_t t) { real_t theta = AngleTo(b); @@ -394,6 +427,22 @@ namespace Godot return left; } + public static Vector3 operator %(Vector3 vec, real_t divisor) + { + vec.x %= divisor; + vec.y %= divisor; + vec.z %= divisor; + return vec; + } + + public static Vector3 operator %(Vector3 vec, Vector3 divisorv) + { + vec.x %= divisorv.x; + vec.y %= divisorv.y; + vec.z %= divisorv.z; + return vec; + } + public static bool operator ==(Vector3 left, Vector3 right) { return left.Equals(right); diff --git a/modules/mono/glue/Managed/Managed.csproj b/modules/mono/glue/Managed/Managed.csproj index 61f738922b..c8eca71199 100644 --- a/modules/mono/glue/Managed/Managed.csproj +++ b/modules/mono/glue/Managed/Managed.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -8,6 +8,7 @@ <RootNamespace>Managed</RootNamespace> <AssemblyName>Managed</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> <DebugSymbols>true</DebugSymbols> @@ -37,4 +38,4 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project> +</Project>
\ No newline at end of file diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 7c30092855..8b9a1380d8 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -167,7 +167,7 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) { int line; Error err = VariantParser::parse(&ss, ret, errs, line); if (err != OK) { - String err_str = "Parse error at line " + itos(line) + ": " + errs; + String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; ERR_PRINTS(err_str); ret = err_str; } @@ -193,8 +193,7 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object PoolByteArray barr; int len; Error err = encode_variant(var, NULL, len, p_full_objects); - ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); - ERR_FAIL_COND_V(err != OK, NULL); + ERR_FAIL_COND_V_MSG(err != OK, NULL, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); barr.resize(len); { @@ -211,6 +210,10 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) { return GDMonoMarshal::mono_string_from_godot(vars); } +MonoObject *godot_icall_DefaultGodotTaskScheduler() { + return GDMonoUtils::mono_cache.task_scheduler_handle->get_target(); +} + void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var); mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); @@ -234,6 +237,9 @@ void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); + + // Dispatcher + mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h index d4e20e2887..a34c0bc50f 100644 --- a/modules/mono/glue/gd_glue.h +++ b/modules/mono/glue/gd_glue.h @@ -75,6 +75,8 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object MonoString *godot_icall_GD_var2str(MonoObject *p_var); +MonoObject *godot_icall_DefaultGodotTaskScheduler(); + // Register internal calls void godot_register_gd_icalls(); diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index 4ad4088514..4c17a6ec9d 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -39,8 +39,8 @@ #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" -#define TOOLS_ASSEMBLY_NAME "GodotTools" -#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor" +#define TOOLS_ASM_NAME "GodotTools" +#define TOOLS_PROJECT_EDITOR_ASM_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/icons/icon_c_#.svg b/modules/mono/icons/icon_c_#.svg new file mode 100644 index 0000000000..69664ca553 --- /dev/null +++ b/modules/mono/icons/icon_c_#.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-0.55228 0-1-0.4478-1-1 0-0.5523 0.44772-1 1-1h1v-2zm1-9-0.56445 2.2578c-0.23643 0.076-0.46689 0.1692-0.68945 0.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-0.11191 0.2211-0.20723 0.4502-0.28516 0.6855l-2.2539 0.5625v2h5.2715c-0.17677-0.3037-0.27041-0.6486-0.27148-1 9.6e-6 -1.1046 0.89543-2 2-2s2 0.8954 2 2c-4.817e-4 0.3512-0.093442 0.6961-0.26953 1h5.2695v-2l-2.2578-0.5645c-0.07594-0.2357-0.1693-0.4655-0.2793-0.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-0.22113-0.1119-0.45028-0.2073-0.68555-0.2852l-0.5625-2.2539zm4 9c-0.71466-1e-4 -1.3751 0.3811-1.7324 1-0.35727 0.6188-0.35727 1.3812 0 2 0.35733 0.6189 1.0178 1.0001 1.7324 1h-2v2h2c0.71466 1e-4 1.3751-0.3811 1.7324-1 0.35727-0.6188 0.35727-1.3812 0-2-0.35733-0.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0"/> +</g> +</svg> diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/mono/mono_gd/android_mono_config.h new file mode 100644 index 0000000000..c5cc244aec --- /dev/null +++ b/modules/mono/mono_gd/android_mono_config.h @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* android_mono_config.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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 */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_MONO_CONFIG_H +#define ANDROID_MONO_CONFIG_H + +#ifdef ANDROID_ENABLED + +#include "core/ustring.h" + +// This function is defined in an auto-generated source file +String get_godot_android_mono_config(); + +#endif // ANDROID_ENABLED + +#endif // ANDROID_MONO_CONFIG_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 2497f083b5..cd111abd4d 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -56,7 +56,7 @@ #endif #ifdef ANDROID_ENABLED -#include "android_mono_config.gen.h" +#include "android_mono_config.h" #endif GDMono *GDMono::singleton = NULL; @@ -265,7 +265,7 @@ void GDMono::initialize() { #ifdef WINDOWS_ENABLED if (assembly_rootdir.empty() || config_dir.empty()) { - ERR_PRINT("Cannot find Mono in the registry"); + ERR_PRINT("Cannot find Mono in the registry."); // Assertion: if they are not set, then they weren't found in the registry CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); } @@ -283,6 +283,18 @@ void GDMono::initialize() { add_mono_shared_libs_dir_to_path(); + { + PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/unhandled_exception_policy", PROPERTY_HINT_ENUM, + vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); + unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); + ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + + if (Engine::get_singleton()->is_editor_hint()) { + // Unhandled exceptions should not terminate the editor + unhandled_exception_policy = POLICY_LOG_ERROR; + } + } + GDMonoAssembly::initialize(); gdmono_profiler_init(); @@ -305,10 +317,15 @@ void GDMono::initialize() { return; #endif - root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); +#if !defined(WINDOWS_ENABLED) && !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) + // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812 + if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) { + OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive"); + } +#endif - ERR_EXPLAIN("Mono: Failed to initialize runtime"); - ERR_FAIL_NULL(root_domain); + root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); @@ -319,11 +336,11 @@ void GDMono::initialize() { print_verbose("Mono: Runtime initialized"); // mscorlib assembly MUST be present at initialization - ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); - ERR_FAIL_COND(!_load_corlib_assembly()); + bool corlib_loaded = _load_corlib_assembly(); + ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly."); - ERR_EXPLAIN("Mono: Failed to load scripts domain"); - ERR_FAIL_COND(_load_scripts_domain() != OK); + Error domain_load_err = _load_scripts_domain(); + ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); #ifdef DEBUG_ENABLED bool debugger_attached = _wait_for_debugger_msecs(500); @@ -339,8 +356,7 @@ void GDMono::initialize() { void GDMono::initialize_load_assemblies() { #ifndef MONO_GLUE_ENABLED - ERR_EXPLAIN("Mono: This binary was built with `mono_glue=no`; cannot load assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies."); #endif // Load assemblies. The API and tools assemblies are required, @@ -349,10 +365,8 @@ void GDMono::initialize_load_assemblies() { _load_api_assemblies(); #if defined(TOOLS_ENABLED) - if (!_load_tools_assemblies()) { - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - CRASH_NOW(); - } + bool tool_assemblies_loaded = _load_tools_assemblies(); + CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); #endif // Load the project's main assembly. This doesn't necessarily need to succeed. @@ -416,12 +430,12 @@ void GDMono::_initialize_and_check_api_hashes() { #ifdef MONO_GLUE_ENABLED if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { - ERR_PRINT("Mono: Core API hash mismatch!"); + ERR_PRINT("Mono: Core API hash mismatch."); } #ifdef TOOLS_ENABLED if (get_api_editor_hash() != GodotSharpBindings::get_editor_api_hash()) { - ERR_PRINT("Mono: Editor API hash mismatch!"); + ERR_PRINT("Mono: Editor API hash mismatch."); } #endif // TOOLS_ENABLED #endif // MONO_GLUE_ENABLED @@ -549,14 +563,14 @@ bool GDMono::_load_corlib_assembly() { } #ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { +bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) { bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ? GDMono::get_singleton()->core_api_assembly_out_of_sync : GDMono::get_singleton()->editor_api_assembly_out_of_sync; - String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String dst_dir = GodotSharpDirs::get_res_assemblies_dir(); + String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); + String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; @@ -567,7 +581,7 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { memdelete(da); if (err != OK) { - ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err)); + ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err) + "."); return false; } } @@ -581,16 +595,16 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { String xml_file = assembly_name + ".xml"; if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy " + xml_file); + WARN_PRINTS("Failed to copy '" + xml_file + "'."); String pdb_file = assembly_name + ".pdb"; if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy " + pdb_file); + WARN_PRINTS("Failed to copy '" + pdb_file + "'."); Error err = da->copy(assembly_src, assembly_dst); if (err != OK) { - ERR_PRINTS("Failed to copy " + assembly_file); + ERR_PRINTS("Failed to copy '" + assembly_file + "'."); return false; } @@ -602,14 +616,14 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { String GDMono::update_api_assemblies_from_prebuilt() { -#define FAIL_REASON(m_out_of_sync, m_prebuilt_exist) \ +#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ ( \ (m_out_of_sync ? \ - String("The assembly is invalidated") : \ - String("The assembly was not found")) + \ - (m_prebuilt_exist ? \ - String(" and the prebuilt assemblies are missing") : \ - String(" and we failed to copy the prebuilt assemblies"))) + String("The assembly is invalidated ") : \ + String("The assembly was not found ")) + \ + (m_prebuilt_exists ? \ + String("and the prebuilt assemblies are missing.") : \ + String("and we failed to copy the prebuilt assemblies."))) bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync; @@ -619,16 +633,28 @@ String GDMono::update_api_assemblies_from_prebuilt() { if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) return String(); // No update needed - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + const int CONFIGS_LEN = 2; + String configs[CONFIGS_LEN] = { String("Debug"), String("Release") }; - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ false); + for (int i = 0; i < CONFIGS_LEN; i++) { + String config = configs[i]; - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ true); + print_verbose("Updating '" + config + "' API assemblies"); + + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config); + String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + } + + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) || + !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + } + } return String(); // Updated successfully @@ -643,7 +669,14 @@ bool GDMono::_load_core_api_assembly() { #ifdef TOOLS_ENABLED // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + + // If running the project manager, load it from the prebuilt API directory + String assembly_dir = !Main::is_project_manager() ? + GodotSharpDirs::get_res_assemblies_dir() : + GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + + String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + bool success = FileAccess::exists(assembly_path) && load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly); #else @@ -674,7 +707,14 @@ bool GDMono::_load_editor_api_assembly() { return true; // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + + // If running the project manager, load it from the prebuilt API directory + String assembly_dir = !Main::is_project_manager() ? + GodotSharpDirs::get_res_assemblies_dir() : + GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + + String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + bool success = FileAccess::exists(assembly_path) && load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly); @@ -699,9 +739,6 @@ bool GDMono::_try_load_api_assemblies() { return false; } - if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) - return false; - #ifdef TOOLS_ENABLED if (!_load_editor_api_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) @@ -713,6 +750,12 @@ bool GDMono::_try_load_api_assemblies() { return false; #endif + // Check if the core API assembly is out of sync only after trying to load the + // editor API assembly. Otherwise, if both assemblies are out of sync, we would + // only update the former as we won't know the latter also needs to be updated. + if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) + return false; + return true; } @@ -723,24 +766,20 @@ void GDMono::_load_api_assemblies() { // The API assemblies are out of sync. Fine, try one more time, but this time // update them from the prebuilt assemblies directory before trying to load them. + // Shouldn't happen. The project manager loads the prebuilt API assemblies + CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies."); + // 1. Unload the scripts domain - if (_unload_scripts_domain() != OK) { - ERR_EXPLAIN("Mono: Failed to unload scripts domain"); - CRASH_NOW(); - } + Error domain_unload_err = _unload_scripts_domain(); + CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain."); // 2. Update the API assemblies String update_error = update_api_assemblies_from_prebuilt(); - if (!update_error.empty()) { - ERR_EXPLAIN(update_error); - CRASH_NOW(); - } + CRASH_COND_MSG(!update_error.empty(), update_error); // 3. Load the scripts domain again - if (_load_scripts_domain() != OK) { - ERR_EXPLAIN("Mono: Failed to load scripts domain"); - CRASH_NOW(); - } + Error domain_load_err = _load_scripts_domain(); + CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); // 4. Try loading the updated assemblies if (!_try_load_api_assemblies()) { @@ -748,24 +787,22 @@ void GDMono::_load_api_assemblies() { if (_are_api_assemblies_out_of_sync()) { if (core_api_assembly_out_of_sync) { - ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync"); + ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync."); } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed"); + ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); } if (editor_api_assembly_out_of_sync) { - ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync"); + ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync."); } CRASH_NOW(); } else { - ERR_EXPLAIN("Failed to load one of the API assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Failed to load one of the API assemblies."); } } #else - ERR_EXPLAIN("Failed to load one of the API assemblies"); - CRASH_NOW(); + CRASH_NOW_MSG("Failed to load one of the API assemblies."); #endif } } @@ -776,8 +813,8 @@ bool GDMono::_load_tools_assemblies() { if (tools_assembly && tools_project_editor_assembly) return true; - bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) && - load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly); + bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) && + load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly); return success; } @@ -814,7 +851,7 @@ void GDMono::_install_trace_listener() { (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener"); install_func((MonoObject **)&exc); if (exc) { - ERR_PRINT("Failed to install System.Diagnostics.Trace listener"); + ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); GDMonoUtils::debug_print_unhandled_exception(exc); } #endif @@ -828,8 +865,7 @@ Error GDMono::_load_scripts_domain() { scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); - ERR_EXPLAIN("Mono: Could not create scripts app domain"); - ERR_FAIL_NULL_V(scripts_domain, ERR_CANT_CREATE); + ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain."); mono_domain_set(scripts_domain, true); @@ -848,7 +884,7 @@ Error GDMono::_unload_scripts_domain() { finalizing_scripts_domain = true; if (!mono_domain_finalize(scripts_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout"); + ERR_PRINT("Mono: Domain finalization timeout."); } finalizing_scripts_domain = false; @@ -874,7 +910,7 @@ Error GDMono::_unload_scripts_domain() { mono_domain_try_unload(domain, (MonoObject **)&exc); if (exc) { - ERR_PRINT("Exception thrown when unloading scripts domain"); + ERR_PRINT("Exception thrown when unloading scripts domain."); GDMonoUtils::debug_unhandled_exception(exc); return FAILED; } @@ -888,20 +924,14 @@ Error GDMono::reload_scripts_domain() { ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); if (scripts_domain) { - Error err = _unload_scripts_domain(); - if (err != OK) { - ERR_PRINT("Mono: Failed to unload scripts domain"); - return err; - } + Error domain_unload_err = _unload_scripts_domain(); + ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain."); } CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded(); - Error err = _load_scripts_domain(); - if (err != OK) { - ERR_PRINT("Mono: Failed to load scripts domain"); - return err; - } + Error domain_load_err = _load_scripts_domain(); + ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain."); // Load assemblies. The API and tools assemblies are required, // the application is aborted if these assemblies cannot be loaded. @@ -909,10 +939,8 @@ Error GDMono::reload_scripts_domain() { _load_api_assemblies(); #if defined(TOOLS_ENABLED) - if (!_load_tools_assemblies()) { - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - CRASH_NOW(); - } + bool tools_assemblies_loaded = _load_tools_assemblies(); + CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); #endif // Load the project's main assembly. Here, during hot-reloading, we do @@ -934,13 +962,13 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { String domain_name = mono_domain_get_friendly_name(p_domain); - print_verbose("Mono: Unloading domain `" + domain_name + "`..."); + print_verbose("Mono: Unloading domain '" + domain_name + "'..."); if (mono_domain_get() == p_domain) mono_domain_set(root_domain, true); if (!mono_domain_finalize(p_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout"); + ERR_PRINT("Mono: Domain finalization timeout."); } mono_gc_collect(mono_gc_max_generation()); @@ -951,7 +979,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { mono_domain_try_unload(p_domain, (MonoObject **)&exc); if (exc) { - ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`"); + ERR_PRINTS("Exception thrown when unloading domain '" + domain_name + "'."); GDMonoUtils::debug_print_unhandled_exception(exc); return FAILED; } @@ -1058,6 +1086,8 @@ GDMono::GDMono() { #ifdef TOOLS_ENABLED api_editor_hash = 0; #endif + + unhandled_exception_policy = POLICY_TERMINATE_APP; } GDMono::~GDMono() { @@ -1066,7 +1096,7 @@ GDMono::~GDMono() { if (scripts_domain) { Error err = _unload_scripts_domain(); if (err != OK) { - ERR_PRINT("Mono: Failed to unload scripts domain"); + ERR_PRINT("Mono: Failed to unload scripts domain."); } } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index deebe5fd50..4f7d3791f7 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -80,6 +80,13 @@ String to_string(Type p_type); class GDMono { +public: + enum UnhandledExceptionPolicy { + POLICY_TERMINATE_APP, + POLICY_LOG_ERROR + }; + +private: bool runtime_initialized; bool finalizing_scripts_domain; @@ -102,6 +109,8 @@ class GDMono { HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; + UnhandledExceptionPolicy unhandled_exception_policy; + void _domain_assemblies_cleanup(uint32_t p_domain_id); bool _are_api_assemblies_out_of_sync(); @@ -156,13 +165,15 @@ public: #endif #ifdef TOOLS_ENABLED - bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type); + bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config); String update_api_assemblies_from_prebuilt(); #endif static GDMono *get_singleton() { return singleton; } - static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); + + UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; } // Do not use these, unless you know what you're doing void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 761c7f6fcb..a82bb42731 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -151,14 +151,14 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, vo } { - // If we find the assembly here, we load it with `mono_assembly_load_from_full`, + // If we find the assembly here, we load it with 'mono_assembly_load_from_full', // which in turn invokes load hooks before returning the MonoAssembly to us. - // One of the load hooks is `load_aot_module`. This hook can end up calling preload hooks - // again for the same assembly in certain in certain circumstances (the `do_load_image` part). + // One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks + // again for the same assembly in certain in certain circumstances (the 'do_load_image' part). // If this is the case and we return NULL due to the no_search condition below, // it will result in an internal crash later on. Therefore we need to return the assembly we didn't - // get yet from `mono_assembly_load_from_full`. Luckily we have the image, which already got it. - // This must be done here. If done in search hooks, it would cause `mono_assembly_load_from_full` + // get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it. + // This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full' // to think another MonoAssembly for this assembly was already loaded, making it delete its own, // when in fact both pointers were the same... This hooks thing is confusing. if (image_corlib_loading) { diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp index 1c10d3c8eb..89a88fcfb2 100644 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -165,8 +165,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base #ifdef DEBUG_ENABLED String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")"; - WARN_PRINTS("Method `" + fullname + "` is hidden by Godot API method. Should be `" + - method->get_full_name_no_class() + "`. In class `" + namespace_name + "." + class_name + "`."); + WARN_PRINTS("Method '" + fullname + "' is hidden by Godot API method. Should be '" + + method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'."); #endif continue; } @@ -184,8 +184,8 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base if (m && m->get_name() != name) { // found String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")"; - WARN_PRINTS("Method `" + fullname + "` should be `" + m->get_full_name_no_class() + - "`. In class `" + namespace_name + "." + class_name + "`."); + WARN_PRINTS("Method '" + fullname + "' should be '" + m->get_full_name_no_class() + + "'. In class '" + namespace_name + "." + class_name + "'."); break; } diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp index 3999658f93..7b8e6f89e9 100644 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -219,16 +219,14 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } default: { - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to convert Variant to a managed enum value of unmarshallable base type."); } } break; } - ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name()); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'."); } break; case MONO_TYPE_ARRAY: @@ -275,8 +273,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ break; } - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to convert Variant to a managed array of unmarshallable element type."); } break; case MONO_TYPE_CLASS: { @@ -351,8 +348,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } } - ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); - ERR_FAIL(); + ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + type_class->get_name() + "'."); } break; case MONO_TYPE_OBJECT: { @@ -508,7 +504,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_ } break; default: { - ERR_PRINTS(String() + "Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding)); + ERR_PRINTS("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + "."); } break; } diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index a84332d4cd..3324ecb3a8 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -48,7 +48,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { CRASH_COND(!unmanaged); - // All mono objects created from the managed world (e.g.: `new Player()`) + // All mono objects created from the managed world (e.g.: 'new Player()') // need to have a CSharpScript in order for their methods to be callable from the unmanaged side Reference *ref = Object::cast_to<Reference>(unmanaged); @@ -108,9 +108,18 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { void unhandled_exception(MonoException *p_exc) { mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well - // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders - GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); - GD_UNREACHABLE(); + + if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { + // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders + GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL); + GD_UNREACHABLE(); + } else { +#ifdef DEBUG_ENABLED + GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); + if (ScriptDebugger::get_singleton()) + ScriptDebugger::get_singleton()->idle_poll(); +#endif + } } } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h index 2d77bde27c..0d82723913 100644 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ b/modules/mono/mono_gd/gd_mono_internals.h @@ -45,7 +45,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); * Do not call this function directly. * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. */ -GD_NORETURN void unhandled_exception(MonoException *p_exc); +void unhandled_exception(MonoException *p_exc); } // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index a6e04e561d..6d91075ce3 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -72,7 +72,7 @@ static void mono_log_callback(const char *log_domain, const char *log_level, con } if (fatal) { - ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: " + GDMonoLog::get_singleton()->get_log_file_path() + "\n"); + ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->get_log_file_path() + "'."); // Make sure to flush before aborting f->flush(); f->close(); @@ -90,8 +90,7 @@ bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { DirAccessRef diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!diraccess, false); Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir); - ERR_EXPLAIN("Failed to create mono logs directory"); - ERR_FAIL_COND_V(logs_mkdir_err != OK, false); + ERR_FAIL_COND_V_MSG(logs_mkdir_err != OK, false, "Failed to create mono logs directory."); } return true; @@ -131,7 +130,7 @@ void GDMonoLog::initialize() { CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8(); if (log_level.length() != 0 && log_level_get_id(log_level.get_data()) == -1) { - ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): " + log_level.get_data()); + ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'."); log_level = CharString(); } @@ -160,7 +159,7 @@ void GDMonoLog::initialize() { log_file = FileAccess::open(log_file_path, FileAccess::WRITE); if (!log_file) { - ERR_PRINT("Mono: Cannot create log file"); + ERR_PRINTS("Mono: Cannot create log file at: " + log_file_path); } } diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 42102ed835..7aac691102 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -276,7 +276,7 @@ String mono_to_utf8_string(MonoString *p_mono_string) { char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); if (!mono_error_ok(&error)) { - ERR_PRINTS(String("Failed to convert MonoString* to UTF-8: ") + mono_error_get_message(&error)); + ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&error) + "'."); mono_error_cleanup(&error); return String(); } @@ -474,8 +474,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty return BOX_ENUM(enum_baseclass, val); } default: { - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed enum value of unmarshallable base type."); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed enum value of unmarshallable base type."); } } } @@ -509,8 +508,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty if (array_type->eklass == CACHED_CLASS_RAW(Color)) return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray()); - ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed array of unmarshallable element type."); } break; case MONO_TYPE_CLASS: { @@ -695,9 +693,8 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty } break; } - ERR_EXPLAIN(String() + "Attempted to convert Variant to an unmarshallable managed type. Name: \'" + - p_type.type_class->get_name() + "\' Encoding: " + itos(p_type.type_encoding)); - ERR_FAIL_V(NULL); + ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to an unmarshallable managed type. Name: '" + + p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); } Variant mono_object_to_variant(MonoObject *p_obj) { @@ -809,8 +806,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) { if (array_type->eklass == CACHED_CLASS_RAW(Color)) return mono_array_to_PoolColorArray((MonoArray *)p_obj); - ERR_EXPLAIN(String() + "Attempted to convert a managed array of unmarshallable element type to Variant."); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant."); } break; case MONO_TYPE_CLASS: { @@ -908,9 +904,8 @@ Variant mono_object_to_variant(MonoObject *p_obj) { } break; } - ERR_EXPLAIN(String() + "Attempted to convert an unmarshallable managed type to Variant. Name: \'" + - type.type_class->get_name() + "\' Encoding: " + itos(type.type_encoding)); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "Attempted to convert an unmarshallable managed type to Variant. Name: '" + + type.type_class->get_name() + "' Encoding: " + itos(type.type_encoding) + "."); } MonoArray *Array_to_mono_array(const Array &p_array) { diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 5987fa8ebb..e385f4c601 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -37,6 +37,10 @@ #include "core/project_settings.h" #include "core/reference.h" +#ifdef TOOLS_ENABLED +#include "editor/script_editor_debugger.h" +#endif + #include "../csharp_script.h" #include "../utils/macros.h" #include "../utils/mutex_utils.h" @@ -48,14 +52,11 @@ namespace GDMonoUtils { MonoCache mono_cache; -#define CACHE_AND_CHECK(m_var, m_val) \ - { \ - CRASH_COND(m_var != NULL); \ - m_var = m_val; \ - if (!m_var) { \ - ERR_EXPLAIN("Mono Cache: Member " #m_var " is null"); \ - ERR_FAIL(); \ - } \ +#define CACHE_AND_CHECK(m_var, m_val) \ + { \ + CRASH_COND(m_var != NULL); \ + m_var = m_val; \ + ERR_FAIL_COND_MSG(!m_var, "Mono Cache: Member " #m_var " is null."); \ } #define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_class, m_val) @@ -449,10 +450,9 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) { } MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { - if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) { - ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'"); - ERR_FAIL_V(NULL); - } + bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, NULL, + "Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'."); MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); ERR_FAIL_NULL_V(mono_object, NULL); @@ -596,8 +596,14 @@ void debug_print_unhandled_exception(MonoException *p_exc) { void debug_send_unhandled_exception_error(MonoException *p_exc) { #ifdef DEBUG_ENABLED - if (!ScriptDebugger::get_singleton()) + if (!ScriptDebugger::get_singleton()) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + ERR_PRINTS(GDMonoUtils::get_exception_name_and_message(p_exc)); + } +#endif return; + } _TLS_RECURSION_GUARD_; @@ -621,7 +627,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { if (unexpected_exc) { GDMonoInternals::unhandled_exception(unexpected_exc); - GD_UNREACHABLE(); + return; } Vector<ScriptLanguage::StackInfo> _si; @@ -655,7 +661,6 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) { void debug_unhandled_exception(MonoException *p_exc) { GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well - GD_UNREACHABLE(); } void print_unhandled_exception(MonoException *p_exc) { @@ -665,11 +670,9 @@ void print_unhandled_exception(MonoException *p_exc) { void set_pending_exception(MonoException *p_exc) { #ifdef NO_PENDING_EXCEPTIONS debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); #else if (get_runtime_invoke_count() == 0) { debug_unhandled_exception(p_exc); - GD_UNREACHABLE(); } if (!mono_runtime_set_pending_exception(p_exc, false)) { diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index f535fbb6d0..d73743bf0b 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -289,7 +289,7 @@ void set_exception_message(MonoException *p_exc, String message); void debug_print_unhandled_exception(MonoException *p_exc); void debug_send_unhandled_exception_error(MonoException *p_exc); -GD_NORETURN void debug_unhandled_exception(MonoException *p_exc); +void debug_unhandled_exception(MonoException *p_exc); void print_unhandled_exception(MonoException *p_exc); /** diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 54d73c971f..189ceaab1b 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -67,10 +67,8 @@ Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { #ifdef DEBUG_ENABLED - if (conn_target_id && !ObjectDB::get_instance(conn_target_id)) { - ERR_EXPLAIN("Resumed after await, but class instance is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(conn_target_id && !ObjectDB::get_instance(conn_target_id), Variant(), + "Resumed after await, but class instance is gone."); #endif if (p_argcount < 1) { diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 2b014c2a45..716c712ccc 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -55,10 +55,7 @@ int sfind(const String &p_text, int p_from) { for (int j = 0; j < src_len; j++) { int read_pos = i + j; - if (read_pos >= len) { - ERR_PRINT("read_pos >= len"); - return -1; - }; + ERR_FAIL_COND_V(read_pos >= len, -1); switch (j) { case 0: @@ -211,14 +208,18 @@ String str_format(const char *p_format, ...) { #endif #if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900 -#define vsnprintf(m_buffer, m_count, m_format, m_argptr) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_argptr) +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy) +#else +#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy) +#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy) #endif String str_format(const char *p_format, va_list p_list) { va_list list; va_copy(list, p_list); - int len = vsnprintf(NULL, 0, p_format, list); + int len = gd_vscprintf(p_format, list); va_end(list); len += 1; // for the trailing '/0' @@ -226,7 +227,7 @@ String str_format(const char *p_format, va_list p_list) { char *buffer(memnew_arr(char, len)); va_copy(list, p_list); - vsnprintf(buffer, len, p_format, list); + gd_vsnprintf(buffer, len, p_format, list); va_end(list); String res(buffer); |