From f53c66a8ec04f06196d7e5eb62abc748cb95c046 Mon Sep 17 00:00:00 2001 From: hzhang Date: Tue, 4 Mar 2025 11:58:14 +0000 Subject: [PATCH] add: Patchable items and frames --- src/AssetProcessGenerator.cs | 27 ++++++ src/ClassSyntaxReceiver.cs | 7 +- src/Generators/FramePatchGenerator.cs | 131 ++++++++++++++++++++++++++ src/InterfaceInfo.cs | 14 +++ 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/Generators/FramePatchGenerator.cs create mode 100644 src/InterfaceInfo.cs diff --git a/src/AssetProcessGenerator.cs b/src/AssetProcessGenerator.cs index 66c9cbc..84264df 100644 --- a/src/AssetProcessGenerator.cs +++ b/src/AssetProcessGenerator.cs @@ -86,6 +86,33 @@ public abstract class AssetProcessGenerator : ISourceGenerator } + protected IEnumerable GetInterfacesWithAttribute(GeneratorExecutionContext context, + INamedTypeSymbol? attribute) + { + if(context.SyntaxReceiver is not ClassSyntaxReceiver receiver) + yield break; + Compilation compilation = context.Compilation; + foreach (InterfaceDeclarationSyntax ifc in receiver.Interfaces) + { + SemanticModel model = compilation.GetSemanticModel(ifc.SyntaxTree); + INamedTypeSymbol? interfaceSymbol = model.GetDeclaredSymbol(ifc) as INamedTypeSymbol; + string filePath = ifc.SyntaxTree.FilePath; + string interfaceName = Path.GetFileNameWithoutExtension(filePath); + if(interfaceSymbol is null) + continue; + if (HasAttribute(interfaceSymbol, attribute)) + yield return new InterfaceInfo + { + DeclarationSyntax = ifc, + InterfaceName = interfaceName, + InterfaceFillName = interfaceSymbol.ToDisplayString(), + Path = filePath, + Symbol = interfaceSymbol, + Namespace = interfaceSymbol.ContainingNamespace.ToDisplayString() + }; + } + } + protected IEnumerable GetClassesWithAttribute(GeneratorExecutionContext context, INamedTypeSymbol? attribute) { diff --git a/src/ClassSyntaxReceiver.cs b/src/ClassSyntaxReceiver.cs index d07047f..ece29fe 100644 --- a/src/ClassSyntaxReceiver.cs +++ b/src/ClassSyntaxReceiver.cs @@ -6,13 +6,14 @@ namespace Polonium.Generators; public class ClassSyntaxReceiver : ISyntaxReceiver { - public List Classes { get; set; } = new (); + public List Classes { get; } = new (); + public List Interfaces { get; } = new (); public void OnVisitSyntaxNode(SyntaxNode syntaxNode) { if (syntaxNode is ClassDeclarationSyntax classDeclaration) - { Classes.Add(classDeclaration); - } + if(syntaxNode is InterfaceDeclarationSyntax interfaceDeclaration) + Interfaces.Add(interfaceDeclaration); } } \ No newline at end of file diff --git a/src/Generators/FramePatchGenerator.cs b/src/Generators/FramePatchGenerator.cs new file mode 100644 index 0000000..ddf5532 --- /dev/null +++ b/src/Generators/FramePatchGenerator.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Polonium.Generators.Generators; +[Generator] +public class FramePatchGenerator : AssetProcessGenerator +{ + public override void Execute(GeneratorExecutionContext context) + { + if(context.SyntaxReceiver is not ClassSyntaxReceiver receiver) + return; + Compilation compilation = context.Compilation; + INamedTypeSymbol? autoPatch = compilation.GetTypeByMetadataName("Polonium.Attributes.AutoPatch"); + INamedTypeSymbol? patchableProperty = compilation.GetTypeByMetadataName("Polonium.Attributes.PatchableProperty"); + foreach (InterfaceInfo inf in GetInterfacesWithAttribute(context, autoPatch)) + { + + PropertyDeclarationSyntax? underlying = inf.DeclarationSyntax.Members + .OfType() + .FirstOrDefault(m => m.AttributeLists + .SelectMany(a => a.Attributes) + .Any(a => + { + SemanticModel model = compilation.GetSemanticModel(a.SyntaxTree); + INamedTypeSymbol? attributeSymbol = model.GetSymbolInfo(a).Symbol?.ContainingType; + return SymbolEqualityComparer.Default.Equals(attributeSymbol, patchableProperty); + })); + if (underlying is null) + continue; + SemanticModel model = compilation.GetSemanticModel(underlying.SyntaxTree); + ITypeSymbol? underlyingType = model.GetTypeInfo(underlying.Type).Type; + ITypeSymbol? hashSet = compilation + .GetTypeByMetadataName("Polonium.DataStructures.PatchableItems.PatchableHashSet`1"); + ITypeSymbol? dictionary = compilation + .GetTypeByMetadataName("Polonium.DataStructures.PatchableItems.PatchableDictionary`2"); + string typeString = ""; + StringBuilder sb = new(); + sb + .AppendLine("using System;") + .AppendLine("using System.Collections.Generic;") + .AppendLine("using System.Linq;") + .AppendLine("using Polonium.Interfaces;") + .AppendLine("using Polonium.DataStructures.PatchableItems;") + .AppendLine("using Polonium.Resources.FramePatches;") + .AppendLine("using Polonium.Resources.FramePatches.Generic;") + .AppendLine($"namespace {inf.Namespace};"); + + string lName = inf.InterfaceName[0] == 'I' ? inf.InterfaceName.Substring(1) : inf.InterfaceName; + if (SymbolEqualityComparer.Default.Equals(underlyingType?.OriginalDefinition, hashSet)) + { + ITypeSymbol? x1 = (underlyingType as INamedTypeSymbol)?.TypeArguments[0]; + sb + .AppendLine($"public partial class {lName}Patch : HashSetFramePatch<{x1?.ToDisplayString()}>") + .AppendLine("{") + .AppendLine($" public override void Patch(IPatchableFrame frame)") + .AppendLine(" {") + .AppendLine($" if(frame is not {inf.InterfaceFillName} sFrame)") + .AppendLine(" return;") + .AppendLine($" switch (sFrame.{underlying.Identifier.ToString()}.UpdateMethod )") + .AppendLine(" {") + .AppendLine(" case UpdateMethods.None:") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Update:") + .AppendLine($" foreach({x1?.ToDisplayString()} x in Updates)") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}.Add(x);") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Replace:") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}.Data = Updates;") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Custom:") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}.CustomUpdate(Updates);") + .AppendLine(" return;") + .AppendLine(" default:") + .AppendLine(" return;") + .AppendLine(" }") + .AppendLine(" }") + .AppendLine("}"); + } + else if (SymbolEqualityComparer.Default.Equals(underlyingType?.OriginalDefinition, dictionary)) + { + ITypeSymbol? x1 = (underlyingType as INamedTypeSymbol)?.TypeArguments[0]; + ITypeSymbol? x2 = (underlyingType as INamedTypeSymbol)?.TypeArguments[1]; + sb + .AppendLine($"public partial class {lName}Patch : DictionaryFramePatch<{x1?.ToDisplayString()}, {x2?.ToDisplayString()}>") + .AppendLine("{") + .AppendLine(" public override void Patch(IPatchableFrame frame)") + .AppendLine(" {") + .AppendLine($" if(frame is not {inf.InterfaceFillName} sFrame)") + .AppendLine(" return;") + .AppendLine($" switch (sFrame.{underlying.Identifier.ToString()}.UpdateMethod )") + .AppendLine(" {") + .AppendLine(" case UpdateMethods.None:") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Update:") + .AppendLine($" foreach({x1?.ToDisplayString()} x in Updates.Keys)") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}[x] = Updates[x];") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Replace:") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}.Data = Updates;") + .AppendLine(" return;") + .AppendLine(" case UpdateMethods.Custom:") + .AppendLine($" sFrame.{underlying.Identifier.ToString()}.CustomUpdate(Updates);") + .AppendLine(" return;") + .AppendLine(" default:") + .AppendLine(" return;") + .AppendLine(" }") + .AppendLine(" }") + .AppendLine("}"); + } + + else + { + sb + + .AppendLine($"public partial class {lName}Patch : FramePatch<{underlyingType?.ToDisplayString()}>") + .AppendLine("{") + .AppendLine(" public override void Patch(IPatchableFrame frame)") + .AppendLine(" {") + .AppendLine($" if(frame is not {inf.InterfaceFillName} sFrame)") + .AppendLine(" return;") + .AppendLine($" sFrame.{underlying.Identifier.ToString()} = Updates;") + .AppendLine(" }") + .AppendLine("}"); + } + context.AddSource($"{lName}Patch.g.cs", sb.ToString()); + + } + } +} \ No newline at end of file diff --git a/src/InterfaceInfo.cs b/src/InterfaceInfo.cs new file mode 100644 index 0000000..12c36b1 --- /dev/null +++ b/src/InterfaceInfo.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Polonium.Generators; + +public class InterfaceInfo +{ + public INamedTypeSymbol Symbol { get; set; } + public InterfaceDeclarationSyntax DeclarationSyntax { get; set; } + public string InterfaceName { get; set; } + public string InterfaceFillName { get; set; } + public string Namespace { get; set; } + public string Path { get; set; } +} \ No newline at end of file