瀏覽代碼

MemoryPack序列化组件树支持 (#518)

* 实体序列化支持
susices 2 年之前
父節點
當前提交
bbd08a9316

+ 82 - 0
Share/Analyzer/Analyzer/EntityHashCodeAnalyzer.cs

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

+ 19 - 12
Share/Analyzer/Config/AnalyzeAssembly.cs

@@ -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";
@@ -48,14 +48,19 @@ namespace ET.Analyzer
         {
             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 =
         {
@@ -71,5 +76,7 @@ namespace ET.Analyzer
         {
             UnityModel, UnityModelView
         };
+        
+        
     }
 }

+ 3 - 1
Share/Analyzer/Config/DiagnosticIds.cs

@@ -53,6 +53,8 @@
         public const string EntitySystemMethodNeedSystemOfAttrAnalyzerRuleId = "ET0025";
 
         public const string FiberLogAnalyzerRuleId = "ET0026";
-
+        
+        
+        public const string EntityHashCodeAnalyzerRuleId = "ET0027";
     }
 }

+ 18 - 0
Share/Analyzer/Config/DiagnosticRules.cs

@@ -377,4 +377,22 @@ namespace ET.Analyzer
                     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 - 0
Share/Analyzer/Share.Analyzer.csproj

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

+ 208 - 0
Share/Share.SourceGenerator/Generator/ETEntitySerializeFormatterGenerator.cs

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

+ 2 - 0
Share/Share.SourceGenerator/Share.SourceGenerator.csproj

@@ -18,6 +18,8 @@
             <Link>Extension\%(RecursiveDir)%(FileName)%(Extension)</Link>
         </Compile>
         <Compile Include="../Analyzer/Config/Definition.cs" />
+        <Compile Include="../../Unity/Assets/Scripts/Core/Helper/StringHashHelper.cs" />
+        <Compile Include="../Analyzer/Config/AnalyzeAssembly.cs" />
     </ItemGroup>
 
     <ItemGroup>

二進制
Unity/Assets/Plugins/Share.SourceGenerator.dll


+ 29 - 4
Unity/Assets/Scripts/Core/Entity/Entity.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using MemoryPack;
 using MongoDB.Bson.Serialization.Attributes;
 
 namespace ET
@@ -15,14 +16,17 @@ namespace ET
         IsNew = 1 << 4,
     }
 
+    [MemoryPackable(GenerateType.NoGenerate)]
     public abstract partial class Entity: DisposeObject, IPool
     {
 #if ENABLE_VIEW && UNITY_EDITOR
         [BsonIgnore]
         [UnityEngine.HideInInspector]
+        [MemoryPackIgnore]
         public UnityEngine.GameObject ViewGO;
 #endif
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public long InstanceId { get; protected set; }
 
@@ -33,6 +37,7 @@ namespace ET
         [BsonIgnore]
         private EntityStatus status = EntityStatus.None;
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public bool IsFromPool
         {
@@ -155,6 +160,7 @@ namespace ET
             }
         }
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public bool IsDisposed => this.InstanceId == 0;
         
@@ -162,6 +168,7 @@ namespace ET
         private Entity parent;
 
         // 可以改变parent,但是不能设置为null
+        [MemoryPackIgnore]
         [BsonIgnore]
         public Entity Parent
         {
@@ -290,6 +297,7 @@ namespace ET
         [BsonIgnore]
         protected IScene iScene;
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public IScene IScene
         {
@@ -368,13 +376,15 @@ namespace ET
             }
         }
 
+        [MemoryPackInclude]
         [BsonElement("Children")]
         [BsonIgnoreIfNull]
-        private List<Entity> childrenDB;
+        protected List<Entity> childrenDB;
 
         [BsonIgnore]
         private SortedDictionary<long, Entity> children;
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public SortedDictionary<long, Entity> Children
         {
@@ -405,13 +415,15 @@ namespace ET
             }
         }
 
+        [MemoryPackInclude]
         [BsonElement("C")]
         [BsonIgnoreIfNull]
-        private List<Entity> componentsDB;
+        protected List<Entity> componentsDB;
 
         [BsonIgnore]
         private SortedDictionary<long, Entity> components;
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public SortedDictionary<long, Entity> Components
         {
@@ -524,7 +536,10 @@ namespace ET
             
             status = EntityStatus.None;
 
-            ObjectPool.Instance.Recycle(this);
+            if (this.IsFromPool)
+            {
+                ObjectPool.Instance.Recycle(this);
+            }
         }
 
         private void AddToComponents(Entity component)
@@ -690,7 +705,17 @@ namespace ET
 
         private static Entity Create(Type type, bool isFromPool)
         {
-            Entity component = (Entity) ObjectPool.Instance.Fetch(type, isFromPool);
+            Entity component;
+            if (isFromPool)
+            {
+                component = (Entity) ObjectPool.Instance.Fetch(type);
+            }
+            else
+            {
+                component = Activator.CreateInstance(type) as Entity;
+            }
+
+            component.IsFromPool = isFromPool;
             component.IsCreated = true;
             component.IsNew = true;
             component.Id = 0;

+ 13 - 0
Unity/Assets/Scripts/Model/Share/EntitySerializeRegister.cs

@@ -0,0 +1,13 @@
+namespace ET
+{
+    public static partial class EntitySerializeRegister
+    {
+        static partial void Register();
+
+        public static void Init()
+        {
+            Register();
+        }
+    }
+}
+

+ 11 - 0
Unity/Assets/Scripts/Model/Share/EntitySerializeRegister.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 78d722c2a1419e14d9a93a223242bd58
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 2 - 1
Unity/Assets/Scripts/Model/Share/Entry.cs

@@ -34,7 +34,8 @@ namespace ET
 
             // 注册Mongo type
             MongoRegister.Init();
-
+            // 注册Entity序列化器
+            EntitySerializeRegister.Init();
             World.Instance.AddSingleton<IdGenerater>();
             World.Instance.AddSingleton<OpcodeType>();
             World.Instance.AddSingleton<ObjectPool>();

+ 4 - 1
Unity/Assets/Scripts/Model/Share/LockStep/LSUnit.cs

@@ -1,11 +1,13 @@
 using System;
+using MemoryPack;
 using MongoDB.Bson.Serialization.Attributes;
 using TrueSync;
 
 namespace ET
 {
     [ChildOf(typeof(LSUnitComponent))]
-    public class LSUnit: LSEntity, IAwake, ISerializeToEntity
+    [MemoryPackable]
+    public partial class LSUnit: LSEntity, IAwake, ISerializeToEntity
     {
         public TSVector Position
         {
@@ -13,6 +15,7 @@ namespace ET
             set;
         }
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public TSVector Forward
         {

+ 8 - 1
Unity/Assets/Scripts/Model/Share/Module/Unit/Unit.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics;
+using MemoryPack;
 using MongoDB.Bson.Serialization.Attributes;
 using Unity.Mathematics;
 
@@ -6,13 +7,16 @@ namespace ET
 {
     [ChildOf(typeof(UnitComponent))]
     [DebuggerDisplay("ViewName,nq")]
-    public class Unit: Entity, IAwake<int>
+    [MemoryPackable]
+    public partial class Unit: Entity, IAwake<int>
     {
         public int ConfigId { get; set; } //配置表id
 
+        [MemoryPackInclude]
         [BsonElement]
         private float3 position; //坐标
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public float3 Position
         {
@@ -25,6 +29,7 @@ namespace ET
             }
         }
 
+        [MemoryPackIgnore]
         [BsonIgnore]
         public float3 Forward
         {
@@ -32,9 +37,11 @@ namespace ET
             set => this.Rotation = quaternion.LookRotation(value, math.up());
         }
         
+        [MemoryPackInclude]
         [BsonElement]
         private quaternion rotation;
         
+        [MemoryPackIgnore]
         [BsonIgnore]
         public quaternion Rotation
         {