Merge pull request #46713 from neikeq/csharp-source-generators-init
Add C# source generator for ScriptPathAttribute
This commit is contained in:
commit
15bd2bf03f
41 changed files with 661 additions and 1359 deletions
3
modules/mono/Directory.Build.props
Normal file
3
modules/mono/Directory.Build.props
Normal file
|
@ -0,0 +1,3 @@
|
|||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
|
||||
</Project>
|
6
modules/mono/SdkPackageVersions.props
Normal file
6
modules/mono/SdkPackageVersions.props
Normal 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>
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -1 +0,0 @@
|
|||
4.0.0-dev3
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Godot.SourceGenerators.Sample
|
||||
{
|
||||
partial class Foo : Godot.Object
|
||||
{
|
||||
}
|
||||
|
||||
// Foo again in the same file
|
||||
partial class Foo
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
|
||||
<CompilerVisibleProperty Include="GodotProjectDir" />
|
||||
<CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace Godot
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableGodotGeneratorsAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue