Merge pull request #46713 from neikeq/csharp-source-generators-init

Add C# source generator for ScriptPathAttribute
This commit is contained in:
Rémi Verschelde 2021-03-07 01:04:47 +01:00 committed by GitHub
commit 15bd2bf03f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 661 additions and 1359 deletions

View file

@ -0,0 +1,3 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
</Project>

View file

@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<PackageVersion_Godot_NET_Sdk>4.0.0-dev4</PackageVersion_Godot_NET_Sdk>
<PackageVersion_Godot_SourceGenerators>4.0.0-dev1</PackageVersion_Godot_SourceGenerators>
</PropertyGroup>
</Project>

View file

@ -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"
)
nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))
with open(package_version_file, mode="r") as f:
version = f.read().strip()
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]

View file

@ -31,6 +31,7 @@
#include "csharp_script.h"
#include <mono/metadata/threads.h>
#include <mono/metadata/tokentype.h>
#include <stdint.h>
#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();
const DotNetScriptLookupInfo *lookup_info =
CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path());
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);
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;
if (script_class) {
#ifdef DEBUG_ENABLED
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == nullptr);
update_script_class_info(this);
_update_exports();
}
return OK;
}
return ERR_FILE_MISSING_DEPENDENCIES;
valid = script_class != nullptr;
if (script_class) {
#ifdef DEBUG_ENABLED
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == nullptr);
update_script_class_info(this);
_update_exports();
}
return OK;
}
ScriptLanguage *CSharpScript::get_language() const {

View file

@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(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<String, DotNetScriptLookupInfo> 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,19 +447,14 @@ public:
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_ const Dictionary &get_scripts_metadata() {
if (scripts_metadata_invalidated) {
_load_scripts_metadata();
}
return scripts_metadata;
}
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);
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;
/* LANGUAGE FUNCTIONS */

View file

@ -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

View file

@ -8,43 +8,33 @@
<PackageId>Godot.NET.Sdk</PackageId>
<Version>4.0.0</Version>
<PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageType>MSBuildSdk</PackageType>
<PackageTags>MSBuildSdk</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!-- Exclude target framework from the package dependencies as we don't include the build output -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
<PropertyGroup>
<NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
</PropertyGroup>
<ItemGroup>
<!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file -->
<None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" Visible="false" />
<None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" Visible="false" />
<!-- SdkPackageVersions.props -->
<Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile">
<PropertyGroup>
<PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion>
</PropertyGroup>
</Target>
<Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion">
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);
description=$(Description);
authors=$(Authors);
version=$(PackageVersion);
packagetype=$(PackageType);
tags=$(PackageTags);
projecturl=$(PackageProjectUrl)
</NuspecProperties>
</PropertyGroup>
</Target>
<None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk" Visible="false" />
</ItemGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg"
DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
</Project>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<description>$description$</description>
<authors>$authors$</authors>
<owners>$authors$</owners>
<projectUrl>$projecturl$</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<tags>$tags$</tags>
<packageTypes>
<packageType name="$packagetype$" />
</packageTypes>
<repository url="$projecturl$" />
</metadata>
<files>
<file src="Sdk\**" target="Sdk" />
</files>
</package>

View file

@ -1,4 +1,6 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
<PropertyGroup>
<!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
<GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
@ -94,6 +96,7 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
<!-- Godot API references -->
<ItemGroup>
<!--
TODO:

View file

@ -14,4 +14,9 @@
-->
<DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<!-- C# source generators -->
<ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
<PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" />
</ItemGroup>
</Project>

View file

@ -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
{
}
}

View file

@ -0,0 +1,11 @@
namespace Godot.SourceGenerators.Sample
{
partial class Foo : Godot.Object
{
}
// Foo again in the same file
partial class Foo
{
}
}

View file

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
<GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
</PropertyGroup>
<PropertyGroup>
<!-- The emitted files are not part of the compilation nor design.
They're only for peeking at the generated sources. Sometimes the
emitted files get corrupted, but that won't break anything. -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj">
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- This file is imported automatically when using PackageReference to
reference Godot.SourceGenerators, but not when using ProjectReference -->
<Import Project="..\Godot.SourceGenerators\Godot.SourceGenerators.props" />
</Project>

View file

@ -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));
}
}
}

View file

@ -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<ClassDeclarationSyntax> 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);
}
}

View file

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<Description>Core C# source generator for Godot projects.</Description>
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.SourceGenerators</PackageId>
<Version>4.0.0</Version>
<PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
<IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<!-- Package the props file -->
<None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
</ItemGroup>
<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
</Project>

View file

@ -0,0 +1,7 @@
<Project>
<ItemGroup>
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
<CompilerVisibleProperty Include="GodotProjectDir" />
<CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
</ItemGroup>
</Project>

View file

@ -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";
}
}

View file

@ -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<ClassDeclarationSyntax>()
// 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<ClassDeclarationSyntax> classDeclarations
)
{
var attributesBuilder = new StringBuilder();
// Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates.
var attributedTrees = new List<SyntaxTree>();
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<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> 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());
}
}
}

View file

@ -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 @@ namespace GodotTools.ProjectEditor
return root != null ? new MSBuildProject(root) : null;
}
private static List<string> 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<string>(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<string> GetIncludeFiles(string projectPath, string itemType)
{
var excluded = new List<string>();
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;

View file

@ -3,7 +3,6 @@
<Target Name="SetPropertiesForGenerateGodotNupkgsVersions">
<PropertyGroup>
<GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile>
<GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile>
</PropertyGroup>
</Target>
@ -18,13 +17,14 @@
</Target>
<Target Name="_GenerateGodotNupkgsVersionsFile"
DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions"
Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)"
Inputs="$(MSBuildProjectFile);$(MSBuildThisFileDirectory);$(MSBuildProjectFile)\..\..\..\SdkPackageVersions.props"
Outputs="$(GeneratedGodotNupkgsVersionsFile)">
<PropertyGroup>
<GenerateGodotNupkgsVersionsCode><![CDATA[
namespace $(RootNamespace) {
public class GeneratedGodotNupkgsVersions {
public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b
public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b
public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b
}
}
]]></GenerateGodotNupkgsVersionsCode>

View file

@ -218,43 +218,12 @@ namespace GodotTools.Build
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

View file

@ -43,8 +43,6 @@ namespace GodotTools.Build
GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
}
BuildManager.GenerateEditorScriptMetadata();
if (!BuildManager.BuildProjectBlocking("Debug"))
return; // Build failed
@ -74,8 +72,6 @@ namespace GodotTools.Build
GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
}
BuildManager.GenerateEditorScriptMetadata();
if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"}))
return; // Build failed

View file

@ -290,7 +290,8 @@ namespace GodotTools.Build
private static readonly (string packageId, string packageVersion)[] PackagesToAdd =
{
("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk)
("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
};
}
}

View file

@ -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 @@ namespace GodotTools
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);
}
}
}

View file

@ -157,9 +157,6 @@ namespace GodotTools.Export
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");

View file

@ -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 @@ namespace GodotTools.Internals
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
public static void EditorRunPlay() => internal_EditorRunPlay();
@ -100,9 +95,6 @@ namespace GodotTools.Internals
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorNodeShowScriptScreen();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();

View file

@ -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<Dictionary> classes, out string errorStr);
public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr)
{
var classesArray = new Array<Dictionary>();
var error = internal_ParseFile(filePath, classesArray, out errorStr);
if (error != Error.Ok)
{
classes = null;
return error;
}
var classesList = new List<ClassDecl>();
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;
}
}
}

View file

@ -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<ScriptClassParser::ClassDecl> &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);

View file

@ -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, "<error>");
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<int?>
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<String> &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<int, NameDecl> 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<int, NameDecl>::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<String> lines = r_source.split("\n", /* p_allow_empty: */ true);
bool *include_lines = memnew_arr(bool, lines.size());
int if_level = -1;
Vector<bool> 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::ClassDecl> ScriptClassParser::get_classes() {
return classes;
}

View file

@ -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<String> base;
bool nested = false;
};
private:
String code;
int idx = 0;
int line = 0;
String error_str;
bool error = false;
Variant value;
Vector<ClassDecl> 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<String> &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<ClassDecl> get_classes();
};
#endif // SCRIPT_CLASS_PARSER_H

View file

@ -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;
}
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Class)]
public class DisableGodotGeneratorsAttribute : Attribute
{
}
}

View file

@ -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;
}
}
}

View file

@ -14,9 +14,12 @@
<ItemGroup>
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
<Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
<Compile Include="Core\Attributes\RPCAttributes.cs" />
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
<Compile Include="Core\Attributes\SignalAttribute.cs" />
<Compile Include="Core\Attributes\ToolAttribute.cs" />
<Compile Include="Core\Basis.cs" />

View file

@ -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;

View file

@ -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<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
if (result) {
match = result->get();
}
} else {
List<GDMonoClass *> 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<String> &p_search_dirs) {
if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) {
return GDMono::get_singleton()->get_corlib_assembly();

View file

@ -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<StringName, GDMonoClass *> gdobject_class_cache;
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
Map<MonoClass *, GDMonoClass *> cached_raw;
@ -111,11 +111,14 @@ public:
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<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());

View file

@ -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));

View file

@ -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;