diff --git a/.editorconfig b/.editorconfig index 68baf5f..e9c0257 100644 --- a/.editorconfig +++ b/.editorconfig @@ -83,7 +83,7 @@ dotnet_remove_unnecessary_suppression_exclusions = none # New line preferences dotnet_style_allow_multiple_blank_lines_experimental = false -dotnet_style_allow_statement_immediately_after_block_experimental = false +dotnet_style_allow_statement_immediately_after_block_experimental = true #### C# Coding Conventions #### @@ -127,6 +127,7 @@ csharp_prefer_system_threading_lock = true csharp_style_namespace_declarations = file_scoped csharp_style_prefer_method_group_conversion = true csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true csharp_style_prefer_top_level_statements = true # Expression-level preferences diff --git a/CustomGeneratorTests/CustomGeneratorTests.csproj b/CustomGeneratorTests/CustomGeneratorTests.csproj index c00eac3..a1152a4 100644 --- a/CustomGeneratorTests/CustomGeneratorTests.csproj +++ b/CustomGeneratorTests/CustomGeneratorTests.csproj @@ -7,7 +7,7 @@ true - + diff --git a/Godot 3 Tests/Godot 3 Tests.csproj b/Godot 3 Tests/Godot 3 Tests.csproj index 4a80e56..7afe95e 100644 --- a/Godot 3 Tests/Godot 3 Tests.csproj +++ b/Godot 3 Tests/Godot 3 Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Godot 4 Tests/Godot 4 Tests.csproj b/Godot 4 Tests/Godot 4 Tests.csproj index 940cdd2..f5d75e4 100644 --- a/Godot 4 Tests/Godot 4 Tests.csproj +++ b/Godot 4 Tests/Godot 4 Tests.csproj @@ -14,7 +14,7 @@ - + \ No newline at end of file diff --git a/Godot 4 Tests/Run.cs b/Godot 4 Tests/Run.cs index 13482f4..c9e204e 100644 --- a/Godot 4 Tests/Run.cs +++ b/Godot 4 Tests/Run.cs @@ -65,6 +65,7 @@ private static IEnumerable> Tests yield return ITest.GetTest; yield return ITest.GetTest; yield return ITest.GetTest; + yield return ITest.GetTest; yield return ITest.GetTest; yield return ITest.GetTest; yield return ITest.GetTest; diff --git a/Godot 4 Tests/TestScenes/Feature132.RpcExtensions/RpcExtensionTests.cs b/Godot 4 Tests/TestScenes/Feature132.RpcExtensions/RpcExtensionTests.cs index f7fde35..e63baae 100644 --- a/Godot 4 Tests/TestScenes/Feature132.RpcExtensions/RpcExtensionTests.cs +++ b/Godot 4 Tests/TestScenes/Feature132.RpcExtensions/RpcExtensionTests.cs @@ -71,15 +71,15 @@ void ITest.ReadyTests() Test(() => My2RpcId(1, 2, 2.2f), "2|2|2.2"); Test(() => My3RpcId(1, 3, 3.3f, EnumTest.C), "3|3|3.3|C"); Test(() => My4RpcId(1), "4|4|4.4|D"); - Test(() => MyXRpcId(1), null, ok: false); // Godot logs error if calling self when CallLocal is false + //Test(() => MyXRpcId(1), null, ok: false); // Godot logs error if calling self when CallLocal is false // Godot logs errors for unknown peer id (but doesn't return error?!?!?) - Test(() => My0RpcId(2), null/*, ok: false*/); - Test(() => My1RpcId(2, 1), null/*, ok: false*/); - Test(() => My2RpcId(2, 2, 2.2f), null/*, ok: false*/); - Test(() => My3RpcId(2, 3, 3.3f, EnumTest.C), null/*, ok: false*/); - Test(() => My4RpcId(2), null/*, ok: false*/); - Test(() => MyXRpcId(2), null/*, ok: false*/); + //Test(() => My0RpcId(2), null/*, ok: false*/); + //Test(() => My1RpcId(2, 1), null/*, ok: false*/); + //Test(() => My2RpcId(2, 2, 2.2f), null/*, ok: false*/); + //Test(() => My3RpcId(2, 3, 3.3f, EnumTest.C), null/*, ok: false*/); + //Test(() => My4RpcId(2), null/*, ok: false*/); + //Test(() => MyXRpcId(2), null/*, ok: false*/); void Test(Func sut, string expected, bool ok = true, [CallerArgumentExpression(nameof(sut))] string test = null) { diff --git a/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/Noise.tres b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/Noise.tres new file mode 100644 index 0000000..9cf4817 --- /dev/null +++ b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/Noise.tres @@ -0,0 +1,6 @@ +[gd_resource type="NoiseTexture3D" load_steps=2 format=3 uid="uid://8ff1k1aycfdw"] + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_lids6"] + +[resource] +noise = SubResource("FastNoiseLite_lids6") diff --git a/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs new file mode 100644 index 0000000..28ea619 --- /dev/null +++ b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs @@ -0,0 +1,65 @@ +using FluentAssertions; +using Godot; +using GodotSharp.BuildingBlocks.TestRunner; + +namespace GodotTests.TestScenes; + +[ShaderGlobals] +public static partial class ShaderGlobals; + +[SceneTree] +public partial class ShaderGlobalsAttributeTests : Node, ITest +{ + void ITest.InitTests() + { + ShaderGlobals.A.Should().BeTrue(); + ShaderGlobals.B.Should().Be(2); + ShaderGlobals.C.Should().Be(0); + ShaderGlobals.D.Should().Be(9); + ShaderGlobals.E.Should().Be(875); + ShaderGlobals.F.Should().Be(new Vector2I(565, 0)); + ShaderGlobals.G.Should().Be(new Vector3I(0, 410, 0)); + ShaderGlobals.H.Should().Be(new Vector4I(0, 475, 0, 180)); + ShaderGlobals.I.Should().Be(new Rect2I(50, 0, 145, 0)); + ShaderGlobals.J.Should().Be(345); + ShaderGlobals.K.Should().Be(new Vector2I(295, 355)); + ShaderGlobals.L.Should().Be(new Vector3I(0, 195, 0)); + ShaderGlobals.M.Should().Be(new Vector4I(0, 0, 275, 0)); + ShaderGlobals.N.Should().Be(0.205f); + ShaderGlobals.O.Should().Be(new Vector2(0.23f, 0.385f)); + ShaderGlobals.P.Should().Be(new Vector3(0.0f, 0.435f, 0.0f)); + ShaderGlobals.Q.Should().Be(new Vector4(0.22f, 0.0f, 0.275f, 0.0f)); + ShaderGlobals.R.Should().Be(new Color(0.517647f, 0.921569f, 0.52549f, 0.788235f)); + ShaderGlobals.S.Should().Be(new Rect2(0.19f, 0.0f, 0.065f, 0.1f)); + ShaderGlobals.T.Should().Be(new Vector4(1.36f, 0.44f, 0.22f, 1.0f)); + ShaderGlobals.U.Should().Be(new Basis(1.0f, 0.205f, 1.37f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f)); + ShaderGlobals.V.Should().Be(new Projection(1.0f, 0.46f, 0.92f, 0.0f, 0.0f, 1.315f, 0.92f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f)); + ShaderGlobals.W.Should().Be(new Transform2D(1.0f, 0.0f, 1.885f, 1.0f, 0.755f, 0.0f)); + ShaderGlobals.X.Should().Be(new Transform3D(1.0f, 0.0f, 0.0f, 0.0f, 1.35f, 0.59f, 0.0f, 0.0f, 1.0f, 0.0f, 0.54f, 0.0f)); + ShaderGlobals.Y.Should().BeNull(); + ShaderGlobals.Y.GetDeclaredType().Should().Be(typeof(Texture2D)); + ShaderGlobals.Z.Should().BeNull(); + ShaderGlobals.Z.GetDeclaredType().Should().Be(typeof(Texture2DArray)); + ShaderGlobals.Ä.Should().Be(GD.Load("res://TestScenes/Feature164.ShaderGlobals/Noise.tres")); + ShaderGlobals.Ö.Should().BeNull(); + ShaderGlobals.Ö.GetDeclaredType().Should().Be(typeof(Cubemap)); + ShaderGlobals.Ü.Should().BeNull(); + ShaderGlobals.Ü.GetDeclaredType().Should().Be(typeof(ExternalTexture)); + + ShaderGlobals.A = false; + ShaderGlobals.B = 7; + ShaderGlobals.N = .777f; + + ShaderGlobals.A.Should().BeFalse(); + ShaderGlobals.B.Should().Be(7); + ShaderGlobals.N.Should().Be(.777f); + + ShaderGlobals.A = ShaderGlobals.Default.A; + ShaderGlobals.B = ShaderGlobals.Default.B; + ShaderGlobals.N = ShaderGlobals.Default.N; + + ShaderGlobals.A.Should().BeTrue(); + ShaderGlobals.B.Should().Be(2); + ShaderGlobals.N.Should().Be(.205f); + } +} diff --git a/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs.uid b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs.uid new file mode 100644 index 0000000..5bd4640 --- /dev/null +++ b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs.uid @@ -0,0 +1 @@ +uid://dm8vgm0v4e6hi diff --git a/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.tscn b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.tscn new file mode 100644 index 0000000..80f2b30 --- /dev/null +++ b/Godot 4 Tests/TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://qlum4js31ian"] + +[ext_resource type="Script" uid="uid://dm8vgm0v4e6hi" path="res://TestScenes/Feature164.ShaderGlobals/ShaderGlobalsAttributeTests.cs" id="1_y0unj"] + +[node name="ShaderGlobalsAttributeTests" type="Node"] +script = ExtResource("1_y0unj") diff --git a/Godot 4 Tests/Utils/Extensions/ReflectionExtensions.cs b/Godot 4 Tests/Utils/Extensions/ReflectionExtensions.cs index 6138bb6..5fd9107 100644 --- a/Godot 4 Tests/Utils/Extensions/ReflectionExtensions.cs +++ b/Godot 4 Tests/Utils/Extensions/ReflectionExtensions.cs @@ -45,4 +45,6 @@ public static void ShouldContain(this Type t, if (Properties is not null) t.Properties().Should().Contain(Properties); if (NestedTypes is not null) t.NestedTypes().Should().Contain(NestedTypes); } + + public static Type GetDeclaredType(this T t) => typeof(T); } diff --git a/Godot 4 Tests/project.godot b/Godot 4 Tests/project.godot index 2cae71b..23688f9 100644 --- a/Godot 4 Tests/project.godot +++ b/Godot 4 Tests/project.godot @@ -164,3 +164,122 @@ avoidance/layer_16="- With Leading - 16" avoidance/layer_17="7 With Leading Numeric 17" avoidance/layer_18=". With Leading . 18" avoidance/layer_19="中文 With Leading Unicode 19" + +[shader_globals] + +a={ +"type": "bool", +"value": true +} +b={ +"type": "bvec2", +"value": 2 +} +c={ +"type": "bvec3", +"value": 0 +} +d={ +"type": "bvec4", +"value": 9 +} +e={ +"type": "int", +"value": 875 +} +f={ +"type": "ivec2", +"value": Vector2i(565, 0) +} +g={ +"type": "ivec3", +"value": Vector3i(0, 410, 0) +} +h={ +"type": "ivec4", +"value": Vector4i(0, 475, 0, 180) +} +i={ +"type": "rect2i", +"value": Rect2i(50, 0, 145, 0) +} +j={ +"type": "uint", +"value": 345 +} +k={ +"type": "uvec2", +"value": Vector2i(295, 355) +} +l={ +"type": "uvec3", +"value": Vector3i(0, 195, 0) +} +m={ +"type": "uvec4", +"value": Vector4i(0, 0, 275, 0) +} +n={ +"type": "float", +"value": 0.205 +} +o={ +"type": "vec2", +"value": Vector2(0.23, 0.385) +} +p={ +"type": "vec3", +"value": Vector3(0, 0.435, 0) +} +q={ +"type": "vec4", +"value": Vector4(0.22, 0, 0.275, 0) +} +r={ +"type": "color", +"value": Color(0.517647, 0.921569, 0.52549, 0.788235) +} +s={ +"type": "rect2", +"value": Rect2(0.19, 0, 0.065, 0.1) +} +t={ +"type": "mat2", +"value": PackedFloat32Array(1.36, 0.44, 0.22, 1) +} +u={ +"type": "mat3", +"value": Basis(1, 0.205, 1.37, 0, 1, 0, 0, 0, 1) +} +v={ +"type": "mat4", +"value": Projection(1, 0.46, 0.92, 0, 0, 1.315, 0.92, 0, 0, 0, 1, 0, 0, 0, 0, 1) +} +w={ +"type": "transform_2d", +"value": Transform2D(1, 0, 1.885, 1, 0.755, 0) +} +x={ +"type": "transform", +"value": Transform3D(1, 0, 0, 0, 1.35, 0.59, 0, 0, 1, 0, 0.54, 0) +} +y={ +"type": "sampler2D", +"value": "" +} +z={ +"type": "sampler2DArray", +"value": "" +} +"ä"={ +"type": "sampler3D", +"value": "res://TestScenes/Feature164.ShaderGlobals/Noise.tres" +} +"ö"={ +"type": "samplerCube", +"value": "" +} +"ü"={ +"type": "samplerExternalOES", +"value": "" +} diff --git a/SourceGenerators/ShaderGlobalsExtensions/Resources.cs b/SourceGenerators/ShaderGlobalsExtensions/Resources.cs new file mode 100644 index 0000000..1b70b16 --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/Resources.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions; + +internal static class Resources +{ + private const string shaderGlobalsTemplate = "GodotSharp.SourceGenerators.ShaderGlobalsExtensions.ShaderGlobalsTemplate.scriban"; + public static readonly string ShaderGlobalsTemplate = Assembly.GetExecutingAssembly().GetEmbeddedResource(shaderGlobalsTemplate); +} diff --git a/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsAttribute.cs b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsAttribute.cs new file mode 100644 index 0000000..779f21f --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsAttribute.cs @@ -0,0 +1,4 @@ +namespace Godot; + +[AttributeUsage(AttributeTargets.Class)] +public sealed class ShaderGlobalsAttribute() : Attribute; diff --git a/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsDataModel.cs b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsDataModel.cs new file mode 100644 index 0000000..f9398e9 --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsDataModel.cs @@ -0,0 +1,88 @@ +using Microsoft.CodeAnalysis; + +namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions; + +internal class ShaderGlobalsDataModel(INamedTypeSymbol symbol, string gdRoot) : ClassDataModel(symbol) +{ + public record ShaderGlobal(string Name, string Type, string Default, string RawName); + + public IList ShaderGlobals { get; } = [.. + ShaderGlobalsScraper + .GetShaderGlobals(gdRoot) + .Select(Convert)]; + + protected override string Str() + => string.Join("\n", ShaderGlobals); + + #region Convert + + private static ShaderGlobal Convert(ShaderGlobalsScraper.ShaderGlobal raw) + { + var csType = ConvertType(raw.Type); + var csValue = ConvertValue(raw.Default) ?? "default"; + return new(raw.Name.ToPascalCase(), csType, csValue, raw.Name); + + string ConvertType(string type) => type switch + { + "bvec2" => "int", + "bvec3" => "int", + "bvec4" => "int", + + "ivec2" => "Vector2I", + "ivec3" => "Vector3I", + "ivec4" => "Vector4I", + + "uvec2" => "Vector2I", + "uvec3" => "Vector3I", + "uvec4" => "Vector4I", + + "vec2" => "Vector2", + "vec3" => "Vector3", + "vec4" => "Vector4", + + "color" => "Color", + + "rect2" => "Rect2", + "rect2i" => "Rect2I", + + "mat2" => "Vector4", + "mat3" => "Basis", + "mat4" => "Projection", + + "transform_2d" => "Transform2D", + "transform" => "Transform3D", + + "sampler2D" => "Texture2D", + "sampler2DArray" => "Texture2DArray", + "sampler3D" => "Texture3D", + "samplerCube" => "Cubemap", + "samplerExternalOES" => "ExternalTexture", + + _ => type, + }; + + string ConvertValue(string v) + { + return v is null ? null : TryAsRes() ?? TryAsCtor() ?? SafeValue(v); + + string TryAsRes() + => v.StartsWith("res://") ? @$"GD.Load<{csType}>(""{v}"")" : null; + + string TryAsCtor() + { + if (v.EndsWith(")")) + { + var args = v.Split('(').Last().TrimEnd(')').Split(',').Select(SafeValue); + return $"new {csType}({string.Join(",", args)})"; + } + + return null; + } + + static string SafeValue(string v) + => v.Contains('.') ? $"{v}f" : v; + } + } + + #endregion +} diff --git a/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsScraper.cs b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsScraper.cs new file mode 100644 index 0000000..d7749c3 --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsScraper.cs @@ -0,0 +1,125 @@ +using System.Text.RegularExpressions; + +namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions; + +internal static class ShaderGlobalsScraper +{ + private const string NameRegexStr = @"^""?(?.+?)""?={$"; + private const string TypeRegexStr = @"^""type"": ""(?.+?)"",$"; + private const string DefaultRegexStr = @"^""value"": ""?(?.*?)""?$"; + + private static readonly Regex NameRegex = new(NameRegexStr, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + private static readonly Regex TypeRegex = new(TypeRegexStr, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + private static readonly Regex DefaultRegex = new(DefaultRegexStr, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + + public record ShaderGlobal(string Name, string Type, string Default); + + public static IEnumerable GetShaderGlobals(string gdRoot) + { + var gdPrj = GD.PRJ(gdRoot); + Log.Debug($"Scraping {gdPrj}"); + + return MatchShaderGlobals(gdPrj); + + static IEnumerable MatchShaderGlobals(string gdFile) + { + var wip = false; + var found = false; + string name = null; + string type = null; + string dflt = null; + foreach (var line in File.ReadLines(gdFile) + .Where(line => line is not "")) + { + Log.Debug($"Line: {line}"); + + if (line is "[shader_globals]") + { + found = true; + continue; + } + + if (found) + { + if (MatchShaderGlobal(line)) + yield return new(name, type, dflt.NullIfEmpty()); + else if (!wip && line.StartsWith("[")) + yield break; + } + } + + bool MatchShaderGlobal(string line) + { + if (name is null) { MatchName(); return false; } + if (type is null) { MatchType(); return false; } + if (dflt is null) { MatchDflt(); return true; } + MatchEnd(); return false; + + void MatchName() + { + if (NameRegex.Match(line) is { Success: true } match) + { + Log.Debug($" - Name {NameRegex.GetGroupsAsStr(match)}"); + name = match.Groups["Name"].Value; + if (wip) EXPECTED("new ShaderGlobal"); + if (type is not null) EXPECTED("name before type"); + if (dflt is not null) EXPECTED("name before default"); + wip = true; + return; + } + + EXPECTED(NameRegexStr); + } + + void MatchType() + { + if (TypeRegex.Match(line) is { Success: true } match) + { + Log.Debug($" - Type {TypeRegex.GetGroupsAsStr(match)}"); + type = match.Groups["Type"].Value; + if (!wip) EXPECTED("within ShaderGlobal"); + if (name is null) EXPECTED("name before type"); + if (dflt is not null) EXPECTED("type before default"); + return; + } + + EXPECTED(TypeRegexStr); + } + + void MatchDflt() + { + if (DefaultRegex.Match(line) is { Success: true } match) + { + Log.Debug($" - Default {DefaultRegex.GetGroupsAsStr(match)}"); + dflt = match.Groups["Default"].Value; + if (!wip) EXPECTED("within ShaderGlobal"); + if (name is null) EXPECTED("name before default"); + if (type is null) EXPECTED("type before default"); + return; + } + + EXPECTED(DefaultRegexStr); + } + + void MatchEnd() + { + if (line is "}") + { + Log.Debug($" - END"); + if (!wip) EXPECTED("within ShaderGlobal"); + if (name is null) EXPECTED("name before end"); + if (type is null) EXPECTED("type before end"); + if (dflt is null) EXPECTED("default before end"); + wip = false; name = null; type = null; dflt = null; + return; + } + + EXPECTED("}"); + } + + void EXPECTED(string reason) + => throw new Exception($"Malformed ShaderGlobal [Expected: {reason}, Found: {line} - Name: {name}, Type: {type}, Default: {dflt}]"); + } + } + } +} diff --git a/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsSourceGenerator.cs b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsSourceGenerator.cs new file mode 100644 index 0000000..820802a --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsSourceGenerator.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Scriban; + +namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions; + +[Generator] +internal class ShaderGlobalsSourceGenerator : SourceGeneratorForDeclaredTypeWithAttribute +{ + private static Template ShaderGlobalsTemplate => field ??= Template.Parse(Resources.ShaderGlobalsTemplate); + + protected override (string GeneratedCode, DiagnosticDetail Error) GenerateCode(Compilation compilation, SyntaxNode node, INamedTypeSymbol symbol, AttributeData attribute, AnalyzerConfigOptions options) + { + var model = new ShaderGlobalsDataModel(symbol, GD.ROOT(node, options)); + Log.Debug($"--- MODEL ---\n{model}\n"); + + var output = ShaderGlobalsTemplate.Render(model, Shared.Utils); + Log.Debug($"--- OUTPUT ---\n{output}\n"); + + return (output, null); + } +} diff --git a/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsTemplate.scriban b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsTemplate.scriban new file mode 100644 index 0000000..4f4f6cb --- /dev/null +++ b/SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsTemplate.scriban @@ -0,0 +1,38 @@ +{{-#####################-}} +{{-#####- CONTENT -#####-}} +{{-#####################-}} + +{{~ capture body ~}} + public static class Default + { +{{~ for x in ShaderGlobals ~}} + public static readonly {{x.Type}} {{x.Name}} = {{x.Default}}; +{{~ end ~}} + } + + private static class Name + { +{{~ for x in ShaderGlobals ~}} + public static readonly StringName {{x.Name}} = "{{x.RawName}}"; +{{~ end ~}} + } + + private static class Value + { +{{~ for x in ShaderGlobals ~}} + public static {{x.Type}} {{x.Name}} = Default.{{x.Name}}; +{{~ end ~}} + } +{{~ for x in ShaderGlobals ~}} + + /// A statically typed wrapper for the shader global {{x.RawName}} defined in godot.project. + /// If the shader global is modified outside of this property, the change will not be reflected in the property. + public static {{x.Type}} {{x.Name}} { get => Value.{{x.Name}}; set => RenderingServer.GlobalShaderParameterSet(Name.{{x.Name}}, Value.{{x.Name}} = value); } +{{~ end ~}} +{{~ end ~}} + +{{-##################-}} +{{-#####- MAIN -#####-}} +{{-##################-}} + +{{~ RenderClass body ~}} diff --git a/SourceGenerators/Utilities/Extensions/GodotExtensions.cs b/SourceGenerators/Utilities/Extensions/GodotExtensions.cs index ab74ed9..6f1161f 100644 --- a/SourceGenerators/Utilities/Extensions/GodotExtensions.cs +++ b/SourceGenerators/Utilities/Extensions/GodotExtensions.cs @@ -96,8 +96,15 @@ public static string ROOT(AnalyzerConfigOptions options, string csPath) public static string ROOT(SyntaxNode node, AnalyzerConfigOptions options) => options.TryGetGodotProjectDir() ?? GetProjectRoot(node.SyntaxTree.FilePath); - public static string RES(string path, string root) - => $"res://{path[root.Length..].Replace("\\", "/").TrimStart('/')}"; + public static string PRJ(string gdRoot) + { + var gdPrj = Path.Combine(gdRoot, GodotProjectFile); + return File.Exists(gdPrj) ? gdPrj : + throw new Exception($"Could not find {GodotProjectFile} in {gdRoot}"); + } + + public static string RES(string csPath, string gdRoot) + => $"res://{csPath[gdRoot.Length..].Replace("\\", "/").TrimStart('/')}"; public static string TSCN(SyntaxNode node, AnalyzerConfigOptions options) => Res("tscn", node, options); public static string TRES(SyntaxNode node, AnalyzerConfigOptions options) => Res("tres", node, options); diff --git a/SourceGenerators/Utilities/Extensions/StringExtensions.cs b/SourceGenerators/Utilities/Extensions/StringExtensions.cs index a3f19dd..5c40bef 100644 --- a/SourceGenerators/Utilities/Extensions/StringExtensions.cs +++ b/SourceGenerators/Utilities/Extensions/StringExtensions.cs @@ -42,18 +42,18 @@ static char LowercaseFirstWord(string x, int i) => i is 0 ? LowercaseFirstChar(x) : UppercaseFirstChar(x); } - public static string TrimPrefix(this string source, string prefix) - => source.StartsWith(prefix, StringComparison.Ordinal) ? source[prefix.Length..] : source; - - public static string TrimSuffix(this string source, string suffix) - => source.EndsWith(suffix, StringComparison.Ordinal) ? source[..^suffix.Length] : source; - public static string AddPrefix(this string source, string prefix) => source.StartsWith(prefix, StringComparison.Ordinal) ? source : $"{prefix}{source}"; public static string AddSuffix(this string source, string suffix) => source.EndsWith(suffix, StringComparison.Ordinal) ? source : $"{source}{suffix}"; + public static string TrimPrefix(this string source, string prefix) + => source.StartsWith(prefix, StringComparison.Ordinal) ? source[prefix.Length..] : source; + + public static string TrimSuffix(this string source, string suffix) + => source.EndsWith(suffix, StringComparison.Ordinal) ? source[..^suffix.Length] : source; + public static string Truncate(this string source, int maxChars) => source.Length <= maxChars ? source : source[..maxChars];