From e2afe700f62aaa503daed0519ff88b0aa1a559c1 Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Sat, 6 Mar 2021 00:12:42 +0100 Subject: [PATCH] Add C# source generator for a new ScriptPath attribute This source generator adds a newly introduced attribute, `ScriptPath` to all classes that: - Are top-level classes (not inner/nested). - Have the `partial` modifier. - Inherit `Godot.Object`. - The class name matches the file name. A build error is thrown if the generator finds a class that meets these conditions but is not declared `partial`, unless the class is annotated with the `DisableGodotGenerators` attribute. We also generate an `AssemblyHasScripts` assembly attribute which Godot uses to get all the script classes in the assembly, eliminating the need for Godot to search them. We can also avoid searching in assemblies that don't have this attribute. This will be good for performance in the future once we support multiple assemblies with Godot script classes. This is an example of what the generated code looks like: ``` using Godot; namespace Foo { [ScriptPathAttribute("res://Player.cs")] // Multiple partial declarations are allowed [ScriptPathAttribute("res://Foo/Player.cs")] partial class Player {} } [assembly:AssemblyHasScripts(new System.Type[] { typeof(Foo.Player) })] ``` The new attributes replace script metadata which we were generating by determining the namespace of script classes with a very simple parser. This fixes several issues with the old approach related to parser errors and conditional compilation. It also makes the task part of the MSBuild project build, rather than a separate step executed by the Godot editor. --- modules/mono/Directory.Build.props | 3 + modules/mono/SdkPackageVersions.props | 6 + .../mono/build_scripts/godot_net_sdk_build.py | 24 +- modules/mono/csharp_script.cpp | 120 +-- modules/mono/csharp_script.h | 34 +- .../editor/Godot.NET.Sdk/Godot.NET.Sdk.sln | 18 + .../Godot.NET.Sdk/Godot.NET.Sdk.csproj | 40 +- .../Godot.NET.Sdk/Godot.NET.Sdk.nuspec | 22 - .../Godot.NET.Sdk_PackageVersion.txt | 1 - .../Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props | 3 + .../Godot.NET.Sdk/Sdk/Sdk.targets | 5 + .../Godot.SourceGenerators.Sample/Bar.cs | 15 + .../Godot.SourceGenerators.Sample/Foo.cs | 11 + .../Godot.SourceGenerators.Sample.csproj | 31 + .../Godot.SourceGenerators/Common.cs | 33 + .../ExtensionMethods.cs | 86 ++ .../Godot.SourceGenerators.csproj | 40 + .../Godot.SourceGenerators.props | 7 + .../Godot.SourceGenerators/GodotClasses.cs | 9 + .../ScriptPathAttributeGenerator.cs | 156 ++++ .../GodotTools.ProjectEditor/ProjectUtils.cs | 47 -- .../GenerateGodotNupkgsVersions.targets | 6 +- .../GodotTools/Build/BuildManager.cs | 31 - .../GodotTools/Build/MSBuildPanel.cs | 4 - .../GodotTools/GodotTools/Build/NuGetUtils.cs | 3 +- .../GodotTools/GodotTools/CsProjOperations.cs | 86 -- .../GodotTools/Export/ExportPlugin.cs | 3 - .../GodotTools/Internals/Internal.cs | 8 - .../GodotTools/Internals/ScriptClassParser.cs | 61 -- modules/mono/editor/editor_internal_calls.cpp | 47 -- modules/mono/editor/script_class_parser.cpp | 753 ------------------ modules/mono/editor/script_class_parser.h | 108 --- .../Attributes/AssemblyHasScriptsAttribute.cs | 22 + .../DisableGodotGeneratorsAttribute.cs | 9 + .../Core/Attributes/ScriptPathAttribute.cs | 15 + .../GodotSharp/GodotSharp/GodotSharp.csproj | 3 + modules/mono/mono_gd/gd_mono.cpp | 1 + modules/mono/mono_gd/gd_mono_assembly.cpp | 103 +-- modules/mono/mono_gd/gd_mono_assembly.h | 13 +- modules/mono/mono_gd/gd_mono_cache.cpp | 10 + modules/mono/mono_gd/gd_mono_cache.h | 5 + 41 files changed, 652 insertions(+), 1350 deletions(-) create mode 100644 modules/mono/Directory.Build.props create mode 100644 modules/mono/SdkPackageVersions.props delete mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec delete mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs delete mode 100644 modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs delete mode 100644 modules/mono/editor/script_class_parser.cpp delete mode 100644 modules/mono/editor/script_class_parser.h create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs diff --git a/modules/mono/Directory.Build.props b/modules/mono/Directory.Build.props new file mode 100644 index 000000000000..fbf864b11b04 --- /dev/null +++ b/modules/mono/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props new file mode 100644 index 000000000000..396443f30e50 --- /dev/null +++ b/modules/mono/SdkPackageVersions.props @@ -0,0 +1,6 @@ + + + 4.0.0-dev4 + 4.0.0-dev1 + + diff --git a/modules/mono/build_scripts/godot_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py index 3bfba0f0f67f..8c5a60d2dbe7 100644 --- a/modules/mono/build_scripts/godot_net_sdk_build.py +++ b/modules/mono/build_scripts/godot_net_sdk_build.py @@ -21,6 +21,18 @@ def build_godot_net_sdk(source, target, env): # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them. +def get_nupkgs_versions(props_file): + import xml.etree.ElementTree as ET + + tree = ET.parse(props_file) + root = tree.getroot() + + return { + "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(), + "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(), + } + + def build(env_mono): assert env_mono["tools"] @@ -30,14 +42,12 @@ def build(env_mono): module_dir = os.getcwd() - package_version_file = os.path.join( - module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt" - ) - - with open(package_version_file, mode="r") as f: - version = f.read().strip() + nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props")) - target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version] + target_filenames = [ + "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"], + "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"], + ] targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames] diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 25cc64393a2c..4fca80fca053 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -31,6 +31,7 @@ #include "csharp_script.h" #include +#include #include #include "core/config/project_settings.h" @@ -1182,46 +1183,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::_load_scripts_metadata() { - scripts_metadata.clear(); +void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { + if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { + return; + } - String scripts_metadata_filename = "scripts_metadata."; + MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); + String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); -#ifdef TOOLS_ENABLED - scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player"; -#else -#ifdef DEBUG_ENABLED - scripts_metadata_filename += "debug"; -#else - scripts_metadata_filename += "release"; -#endif -#endif + dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( + p_class->get_namespace(), p_class->get_name(), p_class); +} - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename); +void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { + if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { + MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); + bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - if (FileAccess::exists(scripts_metadata_path)) { - String old_json; + if (requires_lookup) { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + MonoImage *image = p_assembly->get_image(); - Error ferr = read_all_file_utf8(scripts_metadata_path, old_json); + int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - ERR_FAIL_COND(ferr != OK); + for (int i = 1; i < rows; i++) { + // We don't search inner classes, only top-level. + MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - Variant old_dict_var; - String err_str; - int err_line; - Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line); - if (json_err != OK) { - ERR_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")."); - return; - } + if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { + continue; + } - scripts_metadata = old_dict_var.operator Dictionary(); - scripts_metadata_invalidated = false; + GDMonoClass *current = p_assembly->get_class(mono_class); + if (current) { + lookup_script_for_class(current); + } + } + } else { + // This is the most likely scenario as we use C# source generators + MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - print_verbose("Successfully loaded scripts metadata"); - } else { - if (!Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT("Missing scripts metadata file."); + int length = mono_array_length(script_types); + + for (int i = 0; i < length; i++) { + MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); + ManagedType type = ManagedType::from_reftype(reftype); + ERR_CONTINUE(!type.type_class); + lookup_script_for_class(type.type_class); + } } } } @@ -1300,7 +1311,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() { } #endif - scripts_metadata_invalidated = true; + dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED @@ -3356,45 +3367,34 @@ Error CSharpScript::reload(bool p_keep_state) { GD_MONO_SCOPE_THREAD_ATTACH; - GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); - - if (project_assembly) { - const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path()); - if (script_metadata_var) { - Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"]; - const Variant *namespace_ = script_metadata.getptr("namespace"); - const Variant *class_name = script_metadata.getptr("class_name"); - ERR_FAIL_NULL_V(namespace_, ERR_BUG); - ERR_FAIL_NULL_V(class_name, ERR_BUG); - GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String()); - if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) { - script_class = klass; - } - } else { - // Missing script metadata. Fallback to legacy method - script_class = project_assembly->get_object_derived_class(name); + const DotNetScriptLookupInfo *lookup_info = + CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); + + if (lookup_info) { + GDMonoClass *klass = lookup_info->script_class; + if (klass) { + ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); + script_class = klass; } + } - valid = script_class != nullptr; + valid = script_class != nullptr; - if (script_class) { + if (script_class) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); + print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); + native = GDMonoUtils::get_class_native_base(script_class); - CRASH_COND(native == nullptr); + CRASH_COND(native == nullptr); - update_script_class_info(this); + update_script_class_info(this); - _update_exports(); - } - - return OK; + _update_exports(); } - return ERR_FILE_MISSING_DEPENDENCIES; + return OK; } ScriptLanguage *CSharpScript::get_language() const { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index a31135cd3295..40f7ed4552d4 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance(m_inst)) +struct DotNetScriptLookupInfo { + String class_namespace; + String class_name; + GDMonoClass *script_class = nullptr; + + DotNetScriptLookupInfo() {} // Required by HashMap... + + DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) : + class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) { + } +}; + class CSharpScript : public Script { GDCLASS(CSharpScript, Script); @@ -390,16 +402,15 @@ class CSharpLanguage : public ScriptLanguage { int lang_idx = -1; - Dictionary scripts_metadata; - bool scripts_metadata_invalidated = true; + HashMap dotnet_script_lookup_map; + + void lookup_script_for_class(GDMonoClass *p_class); // For debug_break and debug_break_parse int _debug_parse_err_line = -1; String _debug_parse_err_file; String _debug_error; - void _load_scripts_metadata(); - friend class GDMono; void _on_scripts_domain_unloaded(); @@ -436,18 +447,13 @@ class CSharpLanguage : public ScriptLanguage { void reload_assemblies(bool p_soft_reload); #endif - _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() { - return scripts_metadata_invalidated ? Dictionary() : scripts_metadata; - } + _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } - _FORCE_INLINE_ const Dictionary &get_scripts_metadata() { - if (scripts_metadata_invalidated) { - _load_scripts_metadata(); - } - return scripts_metadata; - } + void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly); - _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } + const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const { + return dotnet_script_lookup_map.getptr(p_script_path); + } String get_name() const override; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index 56c0cb770374..d1868f52efb6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -2,6 +2,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +18,17 @@ Global {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 8304d9e3212f..ef8add0ba8ee 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -8,43 +8,33 @@ Godot.NET.Sdk 4.0.0 - https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk + $(PackageVersion_Godot_NET_Sdk) + https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk + $(RepositoryUrl) MSBuildSdk MSBuildSdk + MIT true - - - Godot.NET.Sdk.nuspec - $(GenerateNuspecDependsOn);SetNuSpecProperties + + true + false - - - $([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim()) - - + + + + + - - - - id=$(PackageId); - description=$(Description); - authors=$(Authors); - version=$(PackageVersion); - packagetype=$(PackageType); - tags=$(PackageTags); - projecturl=$(PackageProjectUrl) - - - + + $(SolutionDir)\..\..\..\..\ $(GodotSourceRootPath)\bin\GodotSharp\ - + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec deleted file mode 100644 index ba68a4da43ca..000000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - $id$ - $version$ - $description$ - $authors$ - $authors$ - $projecturl$ - false - MIT - https://licenses.nuget.org/MIT - $tags$ - - - - - - - - - diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt deleted file mode 100644 index 34749489b9b7..000000000000 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt +++ /dev/null @@ -1 +0,0 @@ -4.0.0-dev3 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 5febcf3175a1..0128f5c70669 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -1,4 +1,6 @@ + + true @@ -94,6 +96,7 @@ $(GodotDefineConstants);$(DefineConstants) + GODOT_REAL_T_IS_DOUBLE;$(DefineConstants) + + + + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs new file mode 100644 index 000000000000..5eaebc44746f --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs @@ -0,0 +1,15 @@ +namespace Godot.SourceGenerators.Sample +{ + partial class Bar : Godot.Object + { + } + + // Foo in another file + partial class Foo + { + } + + partial class NotSameNameAsFile : Godot.Object + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs new file mode 100644 index 000000000000..21a5bfe56069 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs @@ -0,0 +1,11 @@ +namespace Godot.SourceGenerators.Sample +{ + partial class Foo : Godot.Object + { + } + + // Foo again in the same file + partial class Foo + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj new file mode 100644 index 000000000000..24f790986176 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.1 + + + + + $(MSBuildProjectDirectory) + + + + + true + $(BaseIntermediateOutputPath)\GeneratedFiles + + + + + False + + + + + + + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs new file mode 100644 index 000000000000..4867c986e6d9 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators +{ + public static class Common + { + public static void ReportNonPartialGodotScriptClass( + GeneratorExecutionContext context, + ClassDeclarationSyntax cds, INamedTypeSymbol symbol + ) + { + string message = + "Missing partial modifier on declaration of type '" + + $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; + + string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " + + "declared with the partial modifier or annotated with the " + + $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0001", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + cds.GetLocation(), + cds.SyntaxTree.FilePath)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs new file mode 100644 index 000000000000..c3e74822d58b --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators +{ + static class ExtensionMethods + { + public static bool TryGetGlobalAnalyzerProperty( + this GeneratorExecutionContext context, string property, out string? value + ) => context.AnalyzerConfigOptions.GlobalOptions + .TryGetValue("build_property." + property, out value); + + private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName) + { + if (symbol == null) + return false; + + while (true) + { + if (symbol.ToString() == baseName) + { + return true; + } + + if (symbol.BaseType != null) + { + symbol = symbol.BaseType; + continue; + } + + break; + } + + return false; + } + + private static bool IsGodotScriptClass( + this ClassDeclarationSyntax cds, Compilation compilation, + out INamedTypeSymbol? symbol + ) + { + var sm = compilation.GetSemanticModel(cds.SyntaxTree); + + var classTypeSymbol = sm.GetDeclaredSymbol(cds); + + if (classTypeSymbol?.BaseType == null + || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object)) + { + symbol = null; + return false; + } + + symbol = classTypeSymbol; + return true; + } + + public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses( + this IEnumerable source, + Compilation compilation + ) + { + foreach (var cds in source) + { + if (cds.IsGodotScriptClass(compilation, out var symbol)) + yield return (cds, symbol!); + } + } + + public static bool IsPartial(this ClassDeclarationSyntax cds) + => cds.Modifiers.Any(SyntaxKind.PartialKeyword); + + public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol) + => symbol.GetAttributes().Any(attr => + attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr); + + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + public static string FullQualifiedName(this INamedTypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj new file mode 100644 index 000000000000..224d7e5b5a43 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -0,0 +1,40 @@ + + + netstandard2.0 + 8.0 + enable + + + Core C# source generator for Godot projects. + Godot Engine contributors + + Godot.SourceGenerators + 4.0.0 + $(PackageVersion_Godot_SourceGenerators) + https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators + $(RepositoryUrl) + MIT + + true + false + + + + + + + + + + + + + + + + $(SolutionDir)\..\..\..\..\ + $(GodotSourceRootPath)\bin\GodotSharp\ + + + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props new file mode 100644 index 000000000000..f9b47ad5b19d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs new file mode 100644 index 000000000000..29e41d155a83 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -0,0 +1,9 @@ +namespace Godot.SourceGenerators +{ + public static class GodotClasses + { + public const string Object = "Godot.Object"; + public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute"; + public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs new file mode 100644 index 000000000000..150e59e414ef --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPathAttributeGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle) + && toggle == "disabled") + { + return; + } + + // NOTE: IsNullOrEmpty doesn't work well with nullable checks + // ReSharper disable once ReplaceWithStringIsNullOrEmpty + if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) + || godotProjectDir!.Length == 0) + { + throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); + } + + var godotClasses = context.Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType() + // Ignore inner classes + .Where(cds => !(cds.Parent is ClassDeclarationSyntax)) + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute()) + return true; + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + ) + // Ignore classes whose name is not the same as the file name + .Where(x => Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name) + .GroupBy(x => x.symbol) + .ToDictionary(g => g.Key, g => g.Select(x => x.cds)); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, godotProjectDir, + symbol: godotClass.Key, + classDeclarations: godotClass.Value); + } + + if (godotClasses.Count <= 0) + return; + + AddScriptTypesAssemblyAttr(context, godotClasses); + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + string godotProjectDir, + INamedTypeSymbol symbol, + IEnumerable classDeclarations + ) + { + var attributesBuilder = new StringBuilder(); + + // Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates. + var attributedTrees = new List(); + + foreach (var cds in classDeclarations) + { + if (attributedTrees.Contains(cds.SyntaxTree)) + continue; + + attributedTrees.Add(cds.SyntaxTree); + + if (attributesBuilder.Length != 0) + attributesBuilder.Append("\n "); + + attributesBuilder.Append(@"[ScriptPathAttribute(""res://"); + attributesBuilder.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir)); + attributesBuilder.Append(@""")]"); + } + + string classNs = symbol.ContainingNamespace.Name; + string className = symbol.Name; + + var source = $@"using Godot; +namespace {classNs} +{{ + {attributesBuilder} + partial class {className} + {{ + }} +}} +"; + context.AddSource(classNs + "." + className + "_ScriptPath_Generated", + SourceText.From(source, Encoding.UTF8)); + } + + private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context, + Dictionary> godotClasses) + { + var sourceBuilder = new StringBuilder(); + + sourceBuilder.Append("[assembly:"); + sourceBuilder.Append(GodotClasses.AssemblyHasScriptsAttr); + sourceBuilder.Append("(new System.Type[] {"); + + bool first = true; + + foreach (var godotClass in godotClasses) + { + var qualifiedName = godotClass.Key.ToDisplayString( + NullableFlowState.NotNull, SymbolDisplayFormat.FullyQualifiedFormat); + if (!first) + sourceBuilder.Append(", "); + first = false; + sourceBuilder.Append("typeof("); + sourceBuilder.Append(qualifiedName); + sourceBuilder.Append(")"); + } + + sourceBuilder.Append("})]\n"); + + context.AddSource("AssemblyScriptTypes_Generated", + SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + + private static string RelativeToDir(string path, string dir) + { + // Make sure the directory ends with a path separator + dir = Path.Combine(dir, " ").TrimEnd(); + + if (Path.DirectorySeparatorChar == '\\') + dir = dir.Replace("/", "\\") + "\\"; + + var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); + var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); + + // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString + return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 4e2c0f17cce5..cdac9acb259b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,11 +1,5 @@ using System; -using GodotTools.Core; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; using Microsoft.Build.Construction; -using Microsoft.Build.Globbing; namespace GodotTools.ProjectEditor { @@ -31,47 +25,6 @@ public static MSBuildProject Open(string path) return root != null ? new MSBuildProject(root) : null; } - private static List GetAllFilesRecursive(string rootDirectory, string mask) - { - string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); - - // We want relative paths - for (int i = 0; i < files.Length; i++) - { - files[i] = files[i].RelativeToPath(rootDirectory); - } - - return new List(files); - } - - // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future. - public static IEnumerable GetIncludeFiles(string projectPath, string itemType) - { - var excluded = new List(); - var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); - - var root = ProjectRootElement.Open(projectPath); - Debug.Assert(root != null); - - foreach (var item in root.Items) - { - if (string.IsNullOrEmpty(item.Condition)) - continue; - - if (item.ItemType != itemType) - continue; - - string normalizedRemove = item.Remove.NormalizePath(); - - var glob = MSBuildGlob.Parse(normalizedRemove); - excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile))); - } - - includedFiles.RemoveAll(f => excluded.Contains(f)); - - return includedFiles; - } - public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName) { var origRoot = project.Root; diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index 1d382dcb4375..aab2d73bdd8e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -3,7 +3,6 @@ - $(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt $(IntermediateOutputPath)GodotNupkgsVersions.g.cs @@ -18,13 +17,14 @@ diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index b96b0c8175d7..2b6f972529b6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -218,43 +218,12 @@ public static bool EditorBuildCallback() Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - GenerateEditorScriptMetadata(); - if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) return true; // Requested play from an external editor/IDE which already built the project return BuildProjectBlocking("Debug"); } - // NOTE: This will be replaced with C# source generators in 4.0 - public static void GenerateEditorScriptMetadata() - { - string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); - string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - - if (!File.Exists(editorScriptsMetadataPath)) - return; - - try - { - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - } - catch (IOException e) - { - throw new IOException("Failed to copy scripts metadata file.", innerException: e); - } - } - - // NOTE: This will be replaced with C# source generators in 4.0 - public static string GenerateExportedGameScriptMetadata(bool isDebug) - { - string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); - return scriptsMetadataPath; - } - public static void Initialize() { // Build tool settings diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 708ec7345403..ed69c2b833d0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -43,8 +43,6 @@ public void BuildSolution() GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - BuildManager.GenerateEditorScriptMetadata(); - if (!BuildManager.BuildProjectBlocking("Debug")) return; // Build failed @@ -74,8 +72,6 @@ private void RebuildSolution() GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - BuildManager.GenerateEditorScriptMetadata(); - if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"})) return; // Build failed diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index 793ef7fd71c3..16dd1c8c6bae 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -290,7 +290,8 @@ void AddPackage(string packageId, string packageVersion) private static readonly (string packageId, string packageVersion)[] PackagesToAdd = { - ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk) + ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), + ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 1d800b815129..e43f10804d59 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -1,11 +1,6 @@ using Godot; using System; -using System.Linq; -using Godot.Collections; -using GodotTools.Internals; using GodotTools.ProjectEditor; -using File = GodotTools.Utils.File; -using Directory = GodotTools.Utils.Directory; namespace GodotTools { @@ -23,86 +18,5 @@ public static string GenerateGameProject(string dir, string name) return string.Empty; } } - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - private static ulong ConvertToTimestamp(this DateTime value) - { - TimeSpan elapsedTime = value - Epoch; - return (ulong)elapsedTime.TotalSeconds; - } - - private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata) - { - fileMetadata = null; - - var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr); - - if (parseError != Error.Ok) - { - GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}"); - return false; - } - - string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile); - - var firstMatch = classes.FirstOrDefault(classDecl => - classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object. - classDecl.SearchName == searchName // Filter by the name we're looking for - ); - - if (firstMatch == null) - return false; // Not found - - fileMetadata = new Dictionary - { - ["modified_time"] = $"{modifiedTime}", - ["class"] = new Dictionary - { - ["namespace"] = firstMatch.Namespace, - ["class_name"] = firstMatch.Name, - ["nested"] = firstMatch.Nested - } - }; - - return true; - } - - public static void GenerateScriptsMetadata(string projectPath, string outputPath) - { - var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate(); - - bool IsUpToDate(string includeFile, ulong modifiedTime) - { - return metadataDict.TryGetValue(includeFile, out var oldFileVar) && - ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string, - out ulong storedModifiedTime) && storedModifiedTime == modifiedTime; - } - - var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile") - .Select(path => ("res://" + path).SimplifyGodotPath()) - .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp()) - .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value)) - .ToArray(); - - foreach (var pair in outdatedFiles) - { - metadataDict.Remove(pair.Key); - - string includeFile = pair.Key; - - if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata)) - metadataDict[includeFile] = fileMetadata; - } - - string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict); - - string baseDir = outputPath.GetBaseDir(); - - if (!Directory.Exists(baseDir)) - Directory.CreateDirectory(baseDir); - - File.WriteAllText(outputPath, json); - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index e9bb70156207..270be8b6bfde 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -157,9 +157,6 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, int string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug); - AddFile(scriptsMetadataPath, scriptsMetadataPath); - if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) throw new Exception("Failed to build project"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 7e5049e4b7de..77370090ec32 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -1,7 +1,5 @@ -using System; using System.Runtime.CompilerServices; using Godot; -using Godot.Collections; using GodotTools.IdeMessaging.Requests; namespace GodotTools.Internals @@ -42,9 +40,6 @@ public static bool ScriptEditorEdit(Resource resource, int line, int col, bool g public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); - public static Dictionary GetScriptsMetadataOrNothing() => - internal_GetScriptsMetadataOrNothing(typeof(Dictionary)); - public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); public static void EditorRunPlay() => internal_EditorRunPlay(); @@ -100,9 +95,6 @@ public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKin [MethodImpl(MethodImplOptions.InternalCall)] private static extern void internal_EditorNodeShowScriptScreen(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Dictionary internal_GetScriptsMetadataOrNothing(Type dictType); - [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoWindowsInstallRoot(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs deleted file mode 100644 index c72a84c51379..000000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Godot; -using Godot.Collections; - -namespace GodotTools.Internals -{ - public static class ScriptClassParser - { - public class ClassDecl - { - public string Name { get; } - public string Namespace { get; } - public bool Nested { get; } - public long BaseCount { get; } - - public string SearchName => Nested ? - Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : - Name; - - public ClassDecl(string name, string @namespace, bool nested, long baseCount) - { - Name = name; - Namespace = @namespace; - Nested = nested; - BaseCount = baseCount; - } - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Error internal_ParseFile(string filePath, Array classes, out string errorStr); - - public static Error ParseFile(string filePath, out IEnumerable classes, out string errorStr) - { - var classesArray = new Array(); - var error = internal_ParseFile(filePath, classesArray, out errorStr); - if (error != Error.Ok) - { - classes = null; - return error; - } - - var classesList = new List(); - - foreach (var classDeclDict in classesArray) - { - classesList.Add(new ClassDecl( - (string)classDeclDict["name"], - (string)classDeclDict["namespace"], - (bool)classDeclDict["nested"], - (long)classDeclDict["base_count"] - )); - } - - classes = classesList; - - return Error.Ok; - } - } -} diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 667e4a38795c..21efd5893828 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -49,7 +49,6 @@ #include "../utils/osx_utils.h" #include "code_completion.h" #include "godotsharp_export.h" -#include "script_class_parser.h" MonoString *godot_icall_GodotSharpDirs_ResDataDir() { return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); @@ -172,36 +171,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); } -int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { - *r_error_str = nullptr; - - String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); - - ScriptClassParser scp; - Error err = scp.parse_file(filepath); - if (err == OK) { - Array classes = GDMonoMarshal::mono_object_to_variant(p_classes); - const Vector &class_decls = scp.get_classes(); - - for (int i = 0; i < class_decls.size(); i++) { - const ScriptClassParser::ClassDecl &classDecl = class_decls[i]; - - Dictionary classDeclDict; - classDeclDict["name"] = classDecl.name; - classDeclDict["namespace"] = classDecl.namespace_; - classDeclDict["nested"] = classDecl.nested; - classDeclDict["base_count"] = classDecl.base.size(); - classes.push_back(classDeclDict); - } - } else { - String error_str = scp.get_error(); - if (!error_str.is_empty()) { - *r_error_str = GDMonoMarshal::mono_string_from_godot(error_str); - } - } - return err; -} - uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies, MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) { Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies); @@ -289,18 +258,6 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() { EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); } -MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) { - Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); - - MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype); - - int type_encoding = mono_type_get_type(dict_type); - MonoClass *type_class_raw = mono_class_from_mono_type(dict_type); - GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw); - - return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class)); -} - MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { #ifdef WINDOWS_ENABLED String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; @@ -395,9 +352,6 @@ void register_editor_internal_calls() { GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); - // ScriptClassParser - GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile); - // ExportPlugin GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); @@ -416,7 +370,6 @@ void register_editor_internal_calls() { GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp deleted file mode 100644 index e81cbe4ebd89..000000000000 --- a/modules/mono/editor/script_class_parser.cpp +++ /dev/null @@ -1,753 +0,0 @@ -/*************************************************************************/ -/* script_class_parser.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "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. */ -/*************************************************************************/ - -#include "script_class_parser.h" - -#include "core/os/os.h" -#include "core/templates/map.h" - -#include "../utils/string_utils.h" - -const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = { - "[", - "]", - "{", - "}", - ".", - ":", - ",", - "Symbol", - "Identifier", - "String", - "Number", - "<", - ">", - "EOF", - "Error" -}; - -String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) { - ERR_FAIL_INDEX_V(p_token, TK_MAX, ""); - return token_names[p_token]; -} - -ScriptClassParser::Token ScriptClassParser::get_token() { - while (true) { - switch (code[idx]) { - case '\n': { - line++; - idx++; - break; - }; - case 0: { - return TK_EOF; - } break; - case '{': { - idx++; - return TK_CURLY_BRACKET_OPEN; - }; - case '}': { - idx++; - return TK_CURLY_BRACKET_CLOSE; - }; - case '[': { - idx++; - return TK_BRACKET_OPEN; - }; - case ']': { - idx++; - return TK_BRACKET_CLOSE; - }; - case '<': { - idx++; - return TK_OP_LESS; - }; - case '>': { - idx++; - return TK_OP_GREATER; - }; - case ':': { - idx++; - return TK_COLON; - }; - case ',': { - idx++; - return TK_COMMA; - }; - case '.': { - idx++; - return TK_PERIOD; - }; - case '#': { - //compiler directive - while (code[idx] != '\n' && code[idx] != 0) { - idx++; - } - continue; - } break; - case '/': { - switch (code[idx + 1]) { - case '*': { // block comment - idx += 2; - while (true) { - if (code[idx] == 0) { - error_str = "Unterminated comment"; - error = true; - return TK_ERROR; - } else if (code[idx] == '*' && code[idx + 1] == '/') { - idx += 2; - break; - } else if (code[idx] == '\n') { - line++; - } - - idx++; - } - - } break; - case '/': { // line comment skip - while (code[idx] != '\n' && code[idx] != 0) { - idx++; - } - - } break; - default: { - value = "/"; - idx++; - return TK_SYMBOL; - } - } - - continue; // a comment - } break; - case '\'': - case '"': { - bool verbatim = idx != 0 && code[idx - 1] == '@'; - - char32_t begin_str = code[idx]; - idx++; - String tk_string = String(); - while (true) { - if (code[idx] == 0) { - error_str = "Unterminated String"; - 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 - continue; - } - - idx += 1; - break; - } else if (code[idx] == '\\' && !verbatim) { - //escaped characters... - idx++; - char32_t next = code[idx]; - if (next == 0) { - error_str = "Unterminated String"; - error = true; - return TK_ERROR; - } - char32_t res = 0; - - switch (next) { - case 'b': - res = 8; - break; - case 't': - res = 9; - break; - case 'n': - res = 10; - break; - case 'f': - res = 12; - break; - case 'r': - res = 13; - break; - case '\"': - res = '\"'; - break; - case '\\': - res = '\\'; - break; - default: { - res = next; - } break; - } - - tk_string += res; - - } else { - if (code[idx] == '\n') { - line++; - } - tk_string += code[idx]; - } - idx++; - } - - value = tk_string; - - return TK_STRING; - } break; - default: { - if (code[idx] <= 32) { - idx++; - break; - } - - if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) { - value = String::chr(code[idx]); - idx++; - return TK_SYMBOL; - } - - if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { - //a number - const char32_t *rptr; - double number = String::to_float(&code[idx], &rptr); - idx += (rptr - &code[idx]); - value = number; - return TK_NUMBER; - - } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) { - String id; - - id += code[idx]; - idx++; - - while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) { - id += code[idx]; - idx++; - } - - value = id; - return TK_IDENTIFIER; - } else if (code[idx] == '@' && code[idx + 1] == '"') { - // begin of verbatim string - idx++; - } else { - error_str = "Unexpected character."; - error = true; - return TK_ERROR; - } - } - } - } -} - -Error ScriptClassParser::_skip_generic_type_params() { - Token tk; - - while (true) { - tk = get_token(); - - if (tk == TK_IDENTIFIER) { - tk = get_token(); - // Type specifications can end with "?" to denote nullable types, such as IList - if (tk == TK_SYMBOL) { - tk = get_token(); - if (value.operator String() != "?") { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'"; - error = true; - return ERR_PARSE_ERROR; - } - if (tk != TK_OP_GREATER && tk != TK_COMMA) { - error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next."; - error = true; - return ERR_PARSE_ERROR; - } - } - - if (tk == TK_PERIOD) { - while (true) { - tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk != TK_PERIOD) { - break; - } - } - } - - if (tk == TK_OP_LESS) { - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - tk = get_token(); - } - - if (tk == TK_OP_GREATER) { - return OK; - } else if (tk != TK_COMMA) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } else if (tk == TK_OP_LESS) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS); - error = true; - return ERR_PARSE_ERROR; - } else if (tk == TK_OP_GREATER) { - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } -} - -Error ScriptClassParser::_parse_type_full_name(String &r_full_name) { - Token tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - r_full_name += String(value); - - if (code[idx] == '<') { - idx++; - - // We don't mind if the base is generic, but we skip it any ways since this information is not needed - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } - - if (code[idx] != '.') { // We only want to take the next token if it's a period - return OK; - } - - tk = get_token(); - - CRASH_COND(tk != TK_PERIOD); // Assertion - - r_full_name += "."; - - return _parse_type_full_name(r_full_name); -} - -Error ScriptClassParser::_parse_class_base(Vector &r_base) { - String name; - - Error err = _parse_type_full_name(name); - if (err) { - return err; - } - - Token tk = get_token(); - - if (tk == TK_COMMA) { - err = _parse_class_base(r_base); - if (err) { - return err; - } - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - err = _parse_type_constraints(); - if (err) { - return err; - } - - // An open curly bracket was parsed by _parse_type_constraints, so we can exit - } else if (tk == TK_CURLY_BRACKET_OPEN) { - // we are finished when we hit the open curly bracket - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - r_base.push_back(name); - - return OK; -} - -Error ScriptClassParser::_parse_type_constraints() { - Token tk = get_token(); - if (tk != TK_IDENTIFIER) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - if (tk != TK_COLON) { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - while (true) { - tk = get_token(); - if (tk == TK_IDENTIFIER) { - if (String(value) == "where") { - return _parse_type_constraints(); - } - - tk = get_token(); - if (tk == TK_PERIOD) { - while (true) { - tk = get_token(); - - if (tk != TK_IDENTIFIER) { - error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk != TK_PERIOD) { - break; - } - } - } - } - - if (tk == TK_COMMA) { - continue; - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - return _parse_type_constraints(); - } else if (tk == TK_SYMBOL && String(value) == "(") { - tk = get_token(); - if (tk != TK_SYMBOL || String(value) != ")") { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } else if (tk == TK_OP_LESS) { - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } else if (tk == TK_CURLY_BRACKET_OPEN) { - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } -} - -Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) { - Token tk = get_token(); - - if (tk == TK_IDENTIFIER) { - r_name += String(value); - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - - tk = get_token(); - - if (tk == TK_PERIOD) { - r_name += "."; - return _parse_namespace_name(r_name, r_curly_stack); - } else if (tk == TK_CURLY_BRACKET_OPEN) { - r_curly_stack++; - return OK; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } -} - -Error ScriptClassParser::parse(const String &p_code) { - code = p_code; - idx = 0; - line = 0; - error_str = String(); - error = false; - value = Variant(); - classes.clear(); - - Token tk = get_token(); - - Map name_stack; - int curly_stack = 0; - int type_curly_stack = 0; - - while (!error && tk != TK_EOF) { - String identifier = value; - if (tk == TK_IDENTIFIER && (identifier == "class" || identifier == "struct")) { - bool is_class = identifier == "class"; - - tk = get_token(); - - if (tk == TK_IDENTIFIER) { - String name = value; - int at_level = curly_stack; - - ClassDecl class_decl; - - for (Map::Element *E = name_stack.front(); E; E = E->next()) { - const NameDecl &name_decl = E->value(); - - if (name_decl.type == NameDecl::NAMESPACE_DECL) { - if (E != name_stack.front()) { - class_decl.namespace_ += "."; - } - class_decl.namespace_ += name_decl.name; - } else { - class_decl.name += name_decl.name + "."; - } - } - - class_decl.name += name; - class_decl.nested = type_curly_stack > 0; - - bool generic = false; - - while (true) { - tk = get_token(); - - if (tk == TK_COLON) { - Error err = _parse_class_base(class_decl.base); - if (err) { - return err; - } - - curly_stack++; - type_curly_stack++; - - break; - } else if (tk == TK_CURLY_BRACKET_OPEN) { - curly_stack++; - type_curly_stack++; - break; - } else if (tk == TK_OP_LESS && !generic) { - generic = true; - - Error err = _skip_generic_type_params(); - if (err) { - return err; - } - } else if (tk == TK_IDENTIFIER && String(value) == "where") { - Error err = _parse_type_constraints(); - if (err) { - return err; - } - - // An open curly bracket was parsed by _parse_type_constraints, so we can exit - curly_stack++; - type_curly_stack++; - break; - } else { - error_str = "Unexpected token: " + get_token_name(tk); - error = true; - return ERR_PARSE_ERROR; - } - } - - NameDecl name_decl; - name_decl.name = name; - name_decl.type = is_class ? NameDecl::CLASS_DECL : NameDecl::STRUCT_DECL; - name_stack[at_level] = name_decl; - - if (is_class) { - if (!generic) { // no generics, thanks - classes.push_back(class_decl); - } else if (OS::get_singleton()->is_stdout_verbose()) { - String full_name = class_decl.namespace_; - if (full_name.length()) { - full_name += "."; - } - full_name += class_decl.name; - OS::get_singleton()->print("Ignoring generic class declaration: %s\n", full_name.utf8().get_data()); - } - } - } - } else if (tk == TK_IDENTIFIER && identifier == "namespace") { - if (type_curly_stack > 0) { - error_str = "Found namespace nested inside type."; - error = true; - return ERR_PARSE_ERROR; - } - - String name; - int at_level = curly_stack; - - Error err = _parse_namespace_name(name, curly_stack); - if (err) { - return err; - } - - NameDecl name_decl; - name_decl.name = name; - name_decl.type = NameDecl::NAMESPACE_DECL; - name_stack[at_level] = name_decl; - } else if (tk == TK_CURLY_BRACKET_OPEN) { - curly_stack++; - } else if (tk == TK_CURLY_BRACKET_CLOSE) { - curly_stack--; - if (name_stack.has(curly_stack)) { - if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL) { - type_curly_stack--; - } - name_stack.erase(curly_stack); - } - } - - tk = get_token(); - } - - if (!error && tk == TK_EOF && curly_stack > 0) { - error_str = "Reached EOF with missing close curly brackets."; - error = true; - } - - if (error) { - return ERR_PARSE_ERROR; - } - - return OK; -} - -static String get_preprocessor_directive(const String &p_line, int p_from) { - CRASH_COND(p_line[p_from] != '#'); - p_from++; - int i = p_from; - while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') || - (p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) { - i++; - } - return p_line.substr(p_from, i - p_from); -} - -static void run_dummy_preprocessor(String &r_source, const String &p_filepath) { - Vector lines = r_source.split("\n", /* p_allow_empty: */ true); - - bool *include_lines = memnew_arr(bool, lines.size()); - - int if_level = -1; - Vector is_branch_being_compiled; - - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - - const int line_len = line.length(); - - int j; - for (j = 0; j < line_len; j++) { - if (line[j] != ' ' && line[j] != '\t') { - if (line[j] == '#') { - // First non-whitespace char of the line is '#' - include_lines[i] = false; - - String directive = get_preprocessor_directive(line, j); - - if (directive == "if") { - if_level++; - is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]); - } else if (directive == "elif") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.write[if_level] = false; - } else if (directive == "else") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.write[if_level] = false; - } else if (directive == "endif") { - ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'."); - is_branch_being_compiled.remove(if_level); - if_level--; - } - - break; - } else { - // First non-whitespace char of the line is not '#' - include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; - break; - } - } - } - - if (j == line_len) { - // Loop ended without finding a non-whitespace character. - // Either the line was empty or it only contained whitespaces. - include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; - } - } - - r_source.clear(); - - // Custom join ignoring lines removed by the preprocessor - for (int i = 0; i < lines.size(); i++) { - if (i > 0 && include_lines[i - 1]) { - r_source += '\n'; - } - - if (include_lines[i]) { - r_source += lines[i]; - } - } -} - -Error ScriptClassParser::parse_file(const String &p_filepath) { - String source; - - Error ferr = read_all_file_utf8(p_filepath, source); - - 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 + "'."); - - run_dummy_preprocessor(source, p_filepath); - - return parse(source); -} - -String ScriptClassParser::get_error() { - return error_str; -} - -Vector ScriptClassParser::get_classes() { - return classes; -} diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h deleted file mode 100644 index 75a46bb4e591..000000000000 --- a/modules/mono/editor/script_class_parser.h +++ /dev/null @@ -1,108 +0,0 @@ -/*************************************************************************/ -/* script_class_parser.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "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 SCRIPT_CLASS_PARSER_H -#define SCRIPT_CLASS_PARSER_H - -#include "core/string/ustring.h" -#include "core/templates/vector.h" -#include "core/variant/variant.h" - -class ScriptClassParser { -public: - struct NameDecl { - enum Type { - NAMESPACE_DECL, - CLASS_DECL, - STRUCT_DECL - }; - - String name; - Type type = NAMESPACE_DECL; - }; - - struct ClassDecl { - String name; - String namespace_; - Vector base; - bool nested = false; - }; - -private: - String code; - int idx = 0; - int line = 0; - String error_str; - bool error = false; - Variant value; - - Vector classes; - - enum Token { - TK_BRACKET_OPEN, - TK_BRACKET_CLOSE, - TK_CURLY_BRACKET_OPEN, - TK_CURLY_BRACKET_CLOSE, - TK_PERIOD, - TK_COLON, - TK_COMMA, - TK_SYMBOL, - TK_IDENTIFIER, - TK_STRING, - TK_NUMBER, - TK_OP_LESS, - TK_OP_GREATER, - TK_EOF, - TK_ERROR, - TK_MAX - }; - - static const char *token_names[TK_MAX]; - static String get_token_name(Token p_token); - - Token get_token(); - - Error _skip_generic_type_params(); - - Error _parse_type_full_name(String &r_full_name); - Error _parse_class_base(Vector &r_base); - Error _parse_type_constraints(); - Error _parse_namespace_name(String &r_name, int &r_curly_stack); - -public: - Error parse(const String &p_code); - Error parse_file(const String &p_filepath); - - String get_error(); - - Vector get_classes(); -}; - -#endif // SCRIPT_CLASS_PARSER_H diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs new file mode 100644 index 000000000000..ef135da51a9a --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Assembly)] + public class AssemblyHasScriptsAttribute : Attribute + { + private readonly bool requiresLookup; + private readonly System.Type[] scriptTypes; + + public AssemblyHasScriptsAttribute() + { + requiresLookup = true; + } + + public AssemblyHasScriptsAttribute(System.Type[] scriptTypes) + { + requiresLookup = false; + this.scriptTypes = scriptTypes; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs new file mode 100644 index 000000000000..ac6cffceb2fc --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class)] + public class DisableGodotGeneratorsAttribute : Attribute + { + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs new file mode 100644 index 000000000000..12eb1035c3e7 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class ScriptPathAttribute : Attribute + { + private string path; + + public ScriptPathAttribute(string path) + { + this.path = path; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 86a16c17f1c9..a2f05d296e85 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -14,9 +14,12 @@ + + + diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 43a39a4966e8..560788fb3a8d 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -1006,6 +1006,7 @@ bool GDMono::_load_project_assembly() { if (success) { mono_assembly_set_main(project_assembly->get_assembly()); + CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly); } return success; diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 1fe06bfbeee7..a1556bace5b2 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -345,6 +345,45 @@ String GDMonoAssembly::get_path() const { return String::utf8(mono_image_get_filename(image)); } +bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) { +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, false); +#endif + + if (!attrs_fetched) { + fetch_attributes(); + } + + if (!attributes) { + return false; + } + + return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); +} + +MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) { +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, nullptr); +#endif + + if (!attrs_fetched) { + fetch_attributes(); + } + + if (!attributes) { + return nullptr; + } + + return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); +} + +void GDMonoAssembly::fetch_attributes() { + ERR_FAIL_COND(attributes != nullptr); + + attributes = mono_custom_attrs_from_assembly(assembly); + attrs_fetched = true; +} + GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { ERR_FAIL_NULL_V(image, nullptr); @@ -390,70 +429,6 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { return wrapped_class; } -GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) { - GDMonoClass *match = nullptr; - - if (gdobject_class_cache_updated) { - Map::Element *result = gdobject_class_cache.find(p_class); - - if (result) { - match = result->get(); - } - } else { - List nested_classes; - - int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - - for (int i = 1; i < rows; i++) { - MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - - if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { - continue; - } - - GDMonoClass *current = get_class(mono_class); - - if (!current) { - continue; - } - - nested_classes.push_back(current); - - if (!match && current->get_name() == p_class) { - match = current; - } - - while (!nested_classes.is_empty()) { - GDMonoClass *current_nested = nested_classes.front()->get(); - nested_classes.pop_front(); - - void *iter = nullptr; - - while (true) { - MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter); - - if (!raw_nested) { - break; - } - - GDMonoClass *nested_class = get_class(raw_nested); - - if (nested_class) { - gdobject_class_cache.insert(nested_class->get_name(), nested_class); - nested_classes.push_back(nested_class); - } - } - } - - gdobject_class_cache.insert(current->get_name(), current); - } - - gdobject_class_cache_updated = true; - } - - return match; -} - GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector &p_search_dirs) { if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) { return GDMono::get_singleton()->get_corlib_assembly(); diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h index 350fcf32102a..6191c491f480 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -71,13 +71,13 @@ class GDMonoAssembly { MonoImage *image; MonoAssembly *assembly; + bool attrs_fetched = false; + MonoCustomAttrInfo *attributes = nullptr; + #ifdef GD_MONO_HOT_RELOAD uint64_t modified_time = 0; #endif - bool gdobject_class_cache_updated = false; - Map gdobject_class_cache; - HashMap cached_classes; Map cached_raw; @@ -111,11 +111,14 @@ class GDMonoAssembly { String get_path() const; + bool has_attribute(GDMonoClass *p_attr_class); + MonoObject *get_attribute(GDMonoClass *p_attr_class); + + void fetch_attributes(); + GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); GDMonoClass *get_class(MonoClass *p_mono_class); - GDMonoClass *get_object_derived_class(const StringName &p_class); - static String find_assembly(const String &p_name); static void fill_search_dirs(Vector &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index aea467660f06..d66cc29b9ad3 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -148,6 +148,11 @@ void CachedData::clear_godot_api_cache() { class_PuppetSyncAttribute = nullptr; class_GodotMethodAttribute = nullptr; field_GodotMethodAttribute_methodName = nullptr; + class_ScriptPathAttribute = nullptr; + field_ScriptPathAttribute_path = nullptr; + class_AssemblyHasScriptsAttribute = nullptr; + field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; + field_AssemblyHasScriptsAttribute_scriptTypes = nullptr; field_GodotObject_ptr = nullptr; field_StringName_ptr = nullptr; @@ -272,6 +277,11 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute)); CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); + CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute)); + CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path")); + CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute)); + CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup")); + CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes")); CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD)); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index fb75cb4b1cde..51370da45237 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -119,6 +119,11 @@ struct CachedData { GDMonoClass *class_PuppetSyncAttribute; GDMonoClass *class_GodotMethodAttribute; GDMonoField *field_GodotMethodAttribute_methodName; + GDMonoClass *class_ScriptPathAttribute; + GDMonoField *field_ScriptPathAttribute_path; + GDMonoClass *class_AssemblyHasScriptsAttribute; + GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup; + GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes; GDMonoField *field_GodotObject_ptr; GDMonoField *field_StringName_ptr;