Skip to content

Commit

Permalink
MemoryPack序列化组件树支持 (#518)
Browse files Browse the repository at this point in the history
* 实体序列化支持
  • Loading branch information
susices authored Nov 25, 2023
1 parent c0caa9b commit bbd08a9
Show file tree
Hide file tree
Showing 14 changed files with 403 additions and 20 deletions.
82 changes: 82 additions & 0 deletions Share/Analyzer/Analyzer/EntityHashCodeAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace ET.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class EntityHashCodeAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(EntityHashCodeAnalyzerRule.Rule);
public override void Initialize(AnalysisContext context)
{
if (!AnalyzerGlobalSetting.EnableAnalyzer)
{
return;
}

context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction((this.CompilationStartAnalysis));
}

private void CompilationStartAnalysis(CompilationStartAnalysisContext context)
{
var entityHashCodeMap = new ConcurrentDictionary<long, string>();
context.RegisterSemanticModelAction((analysisContext =>
{
if (AnalyzerHelper.IsSemanticModelNeedAnalyze(analysisContext.SemanticModel,UnityCodesPath.UnityModel))
{
AnalyzeSemanticModel(analysisContext, entityHashCodeMap);
}
} ));
}

private void AnalyzeSemanticModel(SemanticModelAnalysisContext analysisContext, ConcurrentDictionary<long, string> entityHashCodeMap)
{
foreach (var classDeclarationSyntax in analysisContext.SemanticModel.SyntaxTree.GetRoot().DescendantNodes<ClassDeclarationSyntax>())
{
var classTypeSymbol = analysisContext.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
if (classTypeSymbol!=null)
{
AnalyzeTypeSymbol(analysisContext, classTypeSymbol,entityHashCodeMap);
}
}
}

private void AnalyzeTypeSymbol(SemanticModelAnalysisContext context, INamedTypeSymbol namedTypeSymbol,ConcurrentDictionary<long, string> entityHashCodeMap)
{
var baseType = namedTypeSymbol.BaseType?.ToString();

// 筛选出实体类
if (baseType!= Definition.EntityType && baseType != Definition.LSEntityType)
{
return;
}

var entityName = namedTypeSymbol.ToString();
var hashCode = entityName.GetLongHashCode();

if (entityHashCodeMap.TryGetValue(hashCode, out var existEntityName))
{
if (existEntityName == entityName)
{
return;
}
var classDeclarationSyntax = namedTypeSymbol.DeclaringSyntaxReferences.First().GetSyntax() as ClassDeclarationSyntax;
Diagnostic diagnostic = Diagnostic.Create(EntityHashCodeAnalyzerRule.Rule, classDeclarationSyntax?.Identifier.GetLocation(), entityName,existEntityName,hashCode.ToString());
context.ReportDiagnostic(diagnostic);
}
else
{
entityHashCodeMap[hashCode] = entityName;
}
}
}
}

31 changes: 19 additions & 12 deletions Share/Analyzer/Config/AnalyzeAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ namespace ET.Analyzer
{
public static class AnalyzeAssembly
{
private const string DotNetCore = "Core";
private const string DotNetModel = "Model";
private const string DotNetHotfix = "Hotfix";
public const string DotNetCore = "Core";
public const string DotNetModel = "Model";
public const string DotNetHotfix = "Hotfix";

private const string UnityCore = "Unity.Core";
private const string UnityModel = "Unity.Model";
private const string UnityHotfix = "Unity.Hotfix";
private const string UnityModelView = "Unity.ModelView";
private const string UnityHotfixView = "Unity.HotfixView";
public const string UnityCore = "Unity.Core";
public const string UnityModel = "Unity.Model";
public const string UnityHotfix = "Unity.Hotfix";
public const string UnityModelView = "Unity.ModelView";
public const string UnityHotfixView = "Unity.HotfixView";

public const string UnityCodes = "Unity.Codes";
public const string UnityAllModel = "Unity.AllModel";
Expand Down Expand Up @@ -48,14 +48,19 @@ public static class AnalyzeAssembly
{
DotNetModel,DotNetHotfix,
};

public static readonly string[] AllLogicModel =
{
DotNetModel, UnityModel,UnityAllModel
};
}

public static class UnityCodesPath
{
private static readonly string UnityModel = @"Unity\Assets\Scripts\Model\".Replace('\\',Path.DirectorySeparatorChar);
private static readonly string UnityModelView = @"Unity\Assets\Scripts\ModelView\".Replace('\\',Path.DirectorySeparatorChar);
private static readonly string UnityHotfix = @"Unity\Assets\Scripts\Hotfix\".Replace('\\',Path.DirectorySeparatorChar);
private static readonly string UnityHotfixView = @"Unity\Assets\Scripts\HotfixView\".Replace('\\',Path.DirectorySeparatorChar);
public static readonly string UnityModel = @"Unity\Assets\Scripts\Model\".Replace('\\',Path.DirectorySeparatorChar);
public static readonly string UnityModelView = @"Unity\Assets\Scripts\ModelView\".Replace('\\',Path.DirectorySeparatorChar);
public static readonly string UnityHotfix = @"Unity\Assets\Scripts\Hotfix\".Replace('\\',Path.DirectorySeparatorChar);
public static readonly string UnityHotfixView = @"Unity\Assets\Scripts\HotfixView\".Replace('\\',Path.DirectorySeparatorChar);

public static readonly string[] AllModelHotfix =
{
Expand All @@ -71,5 +76,7 @@ public static class UnityCodesPath
{
UnityModel, UnityModelView
};


}
}
4 changes: 3 additions & 1 deletion Share/Analyzer/Config/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public static class DiagnosticIds
public const string EntitySystemMethodNeedSystemOfAttrAnalyzerRuleId = "ET0025";

public const string FiberLogAnalyzerRuleId = "ET0026";



public const string EntityHashCodeAnalyzerRuleId = "ET0027";
}
}
18 changes: 18 additions & 0 deletions Share/Analyzer/Config/DiagnosticRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,22 @@ public static class FiberLogAnalyzerRule
true,
Description);
}

public static class EntityHashCodeAnalyzerRule
{
private const string Title = "实体类HashCode禁止重复";

private const string MessageFormat = "{0} 与 {1} 类名HashCode相同:{2}, 请修改类名保证实体类HashCode唯一";

private const string Description = "实体类HashCode禁止重复.";

public static readonly DiagnosticDescriptor Rule =
new DiagnosticDescriptor(DiagnosticIds.EntityHashCodeAnalyzerRuleId,
Title,
MessageFormat,
DiagnosticCategories.All,
DiagnosticSeverity.Error,
true,
Description);
}
}
4 changes: 4 additions & 0 deletions Share/Analyzer/Share.Analyzer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;RS2008</NoWarn>
</PropertyGroup>

<ItemGroup>
<Compile Include="../../Unity/Assets/Scripts/Core/Helper/StringHashHelper.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" PrivateAssets="all" />
<PackageReference Update="NETStandard.Library" PrivateAssets="all" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using System.Collections.Generic;
using System.Text;
using ET.Analyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ET.Generator;

[Generator(LanguageNames.CSharp)]
public class ETEntitySerializeFormatterGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications((() => ETEntitySerializeFormatterSyntaxContextReceiver.Create()));
}

public void Execute(GeneratorExecutionContext context)
{

if (context.SyntaxContextReceiver is not ETEntitySerializeFormatterSyntaxContextReceiver receiver || receiver.entities.Count==0)
{
return;
}

int count = receiver.entities.Count;
string typeHashCodeMapDeclaration = GenerateTypeHashCodeMapDeclaration(receiver);
string serializeContent = GenerateSerializeContent(receiver);
string deserializeContent = GenerateDeserializeContent(receiver);
string genericTypeParam = context.Compilation.AssemblyName == AnalyzeAssembly.DotNetModel? "<TBufferWriter>" : "";
string scopedCode = context.Compilation.AssemblyName == AnalyzeAssembly.DotNetModel? "scoped" : "";
string code = $$"""
#nullable enable
#pragma warning disable CS0108 // hides inherited member
#pragma warning disable CS0162 // Unreachable code
#pragma warning disable CS0164 // This label has not been referenced
#pragma warning disable CS0219 // Variable assigned but never used
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8601 // Possible null reference assignment
#pragma warning disable CS8602
#pragma warning disable CS8604 // Possible null reference argument for parameter
#pragma warning disable CS8619
#pragma warning disable CS8620
#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method
#pragma warning disable CS8765 // Nullability of type of parameter
#pragma warning disable CS9074 // The 'scoped' modifier of parameter doesn't match overridden or implemented member
#pragma warning disable CA1050 // Declare types in namespaces.

using System;
using MemoryPack;

[global::MemoryPack.Internal.Preserve]
public class ETEntitySerializeFormatter : MemoryPackFormatter<global::{{Definition.EntityType}}>
{
static readonly System.Collections.Generic.Dictionary<Type, long> __typeToTag = new({{count}})
{
{{typeHashCodeMapDeclaration}}
};

[global::MemoryPack.Internal.Preserve]
public override void Serialize{{genericTypeParam}}(ref MemoryPackWriter{{genericTypeParam}} writer,{{scopedCode}} ref global::{{Definition.EntityType}}? value)
{

if (value == null)
{
writer.WriteNullUnionHeader();
return;
}

if (__typeToTag.TryGetValue(value.GetType(), out var tag))
{
writer.WriteValue<byte>(global::MemoryPack.MemoryPackCode.WideTag);
writer.WriteValue<long>(tag);
switch (tag)
{
{{serializeContent}}
default:
break;
}
}
else
{
MemoryPackSerializationException.ThrowNotFoundInUnionType(value.GetType(), typeof(global::{{Definition.EntityType}}));
}
}

[global::MemoryPack.Internal.Preserve]
public override void Deserialize(ref MemoryPackReader reader,{{scopedCode}} ref global::{{Definition.EntityType}}? value)
{

bool isNull = reader.ReadValue<byte>() == global::MemoryPack.MemoryPackCode.NullObject;
if (isNull)
{
value = default;
return;
}

var tag = reader.ReadValue<long>();

switch (tag)
{
{{deserializeContent}}
default:
//MemoryPackSerializationException.ThrowInvalidTag(tag, typeof(global::IForExternalUnion));
break;
}
}
}
namespace ET
{
public static partial class EntitySerializeRegister
{
static partial void Register()
{
if (!global::MemoryPack.MemoryPackFormatterProvider.IsRegistered<global::{{Definition.EntityType}}>())
{
global::MemoryPack.MemoryPackFormatterProvider.Register(new ETEntitySerializeFormatter());
}
}
}
}
""";
context.AddSource($"ETEntitySerializeFormatterGenerator.g.cs",code);
}

private string GenerateTypeHashCodeMapDeclaration(ETEntitySerializeFormatterSyntaxContextReceiver receiver)
{
StringBuilder sb = new StringBuilder();
foreach (var entityName in receiver.entities)
{
sb.AppendLine($$""" { typeof(global::{{entityName}}), {{entityName.GetLongHashCode()}} },""");
}
return sb.ToString();
}

private string GenerateSerializeContent(ETEntitySerializeFormatterSyntaxContextReceiver receiver)
{
StringBuilder sb = new StringBuilder();
foreach (var entityName in receiver.entities)
{
sb.AppendLine($$""" case {{entityName.GetLongHashCode()}}: writer.WritePackable(System.Runtime.CompilerServices.Unsafe.As<global::{{Definition.EntityType}}?, global::{{entityName}}>(ref value)); break;""");
}
return sb.ToString();
}

private string GenerateDeserializeContent(ETEntitySerializeFormatterSyntaxContextReceiver receiver)
{
StringBuilder sb = new StringBuilder();
foreach (var entityName in receiver.entities)
{
sb.AppendLine($$"""
case {{entityName.GetLongHashCode()}}:
if(value is global::{{entityName}})
{
reader.ReadPackable(ref System.Runtime.CompilerServices.Unsafe.As<global::{{Definition.EntityType}}?, global::{{entityName}}>(ref value));
}else{
value = (global::{{entityName}})reader.ReadPackable<global::{{entityName}}>();
}
break;
""");
}
return sb.ToString();
}

class ETEntitySerializeFormatterSyntaxContextReceiver : ISyntaxContextReceiver
{
internal static ISyntaxContextReceiver Create()
{
return new ETEntitySerializeFormatterSyntaxContextReceiver();
}

public HashSet<string> entities = new HashSet<string>();

public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
if (!AnalyzerHelper.IsSemanticModelNeedAnalyze(context.SemanticModel, UnityCodesPath.UnityModel))
{
return;
}

if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax)
{
return;
}

var classTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
if (classTypeSymbol==null)
{
return;
}

var baseType = classTypeSymbol.BaseType?.ToString();

// 筛选出实体类
if (baseType!= Definition.EntityType && baseType != Definition.LSEntityType)
{
return;
}

if (!classTypeSymbol.HasAttribute("MemoryPack.MemoryPackableAttribute"))
{
return;
}

entities.Add(classTypeSymbol.ToString());
}
}
}
Loading

0 comments on commit bbd08a9

Please sign in to comment.