From 270f1b8265ab9f67d65058cc76dc1ae629e4a527 Mon Sep 17 00:00:00 2001 From: Loretta Date: Tue, 10 Feb 2026 20:24:47 +0100 Subject: [PATCH] Refactor: make AcBinarySerializer fully generic on output Major internal refactor: AcBinarySerializer and BinarySerializationContext are now generic on TOutput : BinaryOutputBase, enabling JIT devirtualization and eliminating virtual dispatch in hot serialization loops. All serialization logic (WriteValue, WriteObject, WriteArray, etc.) is now generic on TOutput and delegates buffer operations to the output instance (ArrayBinaryOutput or BufferWriterBinaryOutput). Context pooling is now per output type. All buffer management is moved to output classes. The public API is unchanged, but the internal architecture is now fully generic and ready for further JIT optimizations. Also disables the source generator and sets UseMetadata=false by default. --- .claude/settings.local.json | 3 +- AyCode.Core.Serializers.Console/Program.cs | 24 +- .../AcBinarySourceGenerator.cs | 1353 +++++++++-------- ...erializer.BinaryDeserializeTypeMetadata.cs | 2 +- ...rySerializer.BinarySerializationContext.cs | 695 +-------- ...ySerializer.BinarySerializeTypeMetadata.cs | 2 +- .../Binaries/AcBinarySerializer.ScanPass.cs | 9 +- .../Binaries/AcBinarySerializer.cs | 661 ++++---- .../Binaries/AcBinarySerializerOptions.cs | 2 +- .../Serializers/Binaries/ArrayBinaryOutput.cs | 15 + .../Serializers/Binaries/BinaryOutputBase.cs | 113 +- 11 files changed, 1129 insertions(+), 1750 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a3e4284..2fd71d1 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -31,7 +31,8 @@ "Bash(Remove-Item \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Toons\\\\AcToonSerializer.RelationshipDetection.cs\")", "Bash(find:*)", "Bash(dir:*)", - "Bash(git stash:*)" + "Bash(git stash:*)", + "WebFetch(domain:github.com)" ] } } diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index 4da051a..38052b5 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -51,6 +51,9 @@ public static class Program #else private static int WarmupIterations = 2000; private static int TestIterations = 1000; + + //private static int WarmupIterations = 5000; + //private static int TestIterations = 2000; #endif public static void Main(string[] args) @@ -209,11 +212,16 @@ public static class Program { // AcBinary variants - new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), - new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), + //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), + //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), + //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), + //new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern), + + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryDefault), + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoRef), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), - new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern), - + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoIntern), + // AcJson new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault), @@ -550,8 +558,8 @@ public static class Program } System.Console.WriteLine($"└{"─".PadRight(6, '─')}─{"─".PadRight(27, '─')}┴{"─".PadRight(12, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(14, '─')}┴{"─".PadRight(13, '─')}┘"); - System.Console.WriteLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); - System.Console.WriteLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); + //System.Console.WriteLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); + //System.Console.WriteLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); } // Summary: Best serializer for each category @@ -751,8 +759,8 @@ public static class Program sb.AppendLine($" {SerializerAcBinaryDefault} vs {SerializerMessagePack}: Size {sizePct:+0;-0}% │ Ser {serPct:+0;-0}% │ Des {desPct:+0;-0}% │ RT {rtPct:+0;-0}%"); } - sb.AppendLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); - sb.AppendLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); + //sb.AppendLine($"GrowBufferCount: {AcBinarySerializer.GrowBufferCount}"); + //sb.AppendLine($"GrowBufferTotalBytes: {AcBinarySerializer.GrowBufferTotalBytes:N0} bytes"); } diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index 839dd44..4d88643 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -1,778 +1,779 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; +//using System; +//using System.Collections.Generic; +//using System.Collections.Immutable; +//using System.Linq; +//using System.Text; +//using Microsoft.CodeAnalysis; +//using Microsoft.CodeAnalysis.CSharp.Syntax; +//using Microsoft.CodeAnalysis.Text; -namespace AyCode.Core.Serializers.SourceGenerator; +//namespace AyCode.Core.Serializers.SourceGenerator; -/// -/// Source Generator for AcBinary serialization. -/// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable]. -/// -[Generator] -public class AcBinarySourceGenerator : IIncrementalGenerator -{ - private const string AcBinarySerializableAttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute"; +///// +///// Source Generator for AcBinary serialization. +///// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable]. +///// +//[Generator] +//public class AcBinarySourceGenerator : IIncrementalGenerator +//{ +// private const string AcBinarySerializableAttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute"; - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Find all classes with [AcBinarySerializable] attribute - var classDeclarations = context.SyntaxProvider - .ForAttributeWithMetadataName( - AcBinarySerializableAttributeName, - predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax, - transform: static (ctx, _) => GetClassInfo(ctx)) - .Where(static info => info != null); +// public void Initialize(IncrementalGeneratorInitializationContext context) +// { +// // Find all classes with [AcBinarySerializable] attribute +// var classDeclarations = context.SyntaxProvider +// .ForAttributeWithMetadataName( +// AcBinarySerializableAttributeName, +// predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax, +// transform: static (ctx, _) => GetClassInfo(ctx)) +// .Where(static info => info != null); - // Combine with compilation - var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); +// // Combine with compilation +// var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); - // Generate source - context.RegisterSourceOutput(compilationAndClasses, - static (spc, source) => Execute(source.Left, source.Right, spc)); - } +// // Generate source +// context.RegisterSourceOutput(compilationAndClasses, +// static (spc, source) => Execute(source.Left, source.Right, spc)); +// } - private static SerializableClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context) - { - if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol)) - return null; +// private static SerializableClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context) +// { +// if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol)) +// return null; - var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace - ? string.Empty - : typeSymbol.ContainingNamespace.ToDisplayString(); +// var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace +// ? string.Empty +// : typeSymbol.ContainingNamespace.ToDisplayString(); - var className = typeSymbol.Name; - var fullTypeName = typeSymbol.ToDisplayString(); - var isStruct = typeSymbol.IsValueType; +// var className = typeSymbol.Name; +// var fullTypeName = typeSymbol.ToDisplayString(); +// var isStruct = typeSymbol.IsValueType; - // Check if this is a nested type - var isNestedType = typeSymbol.ContainingType != null; +// // Check if this is a nested type +// var isNestedType = typeSymbol.ContainingType != null; - // For nested types, we need the full containing type path for method signatures - // e.g. "OuterClass.InnerClass" instead of just "InnerClass" - var typeNameForSignature = isNestedType - ? GetNestedTypeName(typeSymbol) - : className; +// // For nested types, we need the full containing type path for method signatures +// // e.g. "OuterClass.InnerClass" instead of just "InnerClass" +// var typeNameForSignature = isNestedType +// ? GetNestedTypeName(typeSymbol) +// : className; - // Get all public properties with getter and setter - // DUPLICATED LOGIC: Same filtering as TypeMetadataBase.GetSerializableProperties() - var properties = new List(); - foreach (var member in typeSymbol.GetMembers()) - { - if (member is IPropertySymbol p && - p.DeclaredAccessibility == Accessibility.Public && - p.GetMethod != null && - p.SetMethod != null && - !p.IsIndexer && - !p.IsStatic) - { - properties.Add(new PropertyInfo( - p.Name, - p.Type.ToDisplayString(), - GetPropertyTypeKind(p.Type), - p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableValueType(p.Type))); - } - } +// // Get all public properties with getter and setter +// // DUPLICATED LOGIC: Same filtering as TypeMetadataBase.GetSerializableProperties() +// var properties = new List(); +// foreach (var member in typeSymbol.GetMembers()) +// { +// if (member is IPropertySymbol p && +// p.DeclaredAccessibility == Accessibility.Public && +// p.GetMethod != null && +// p.SetMethod != null && +// !p.IsIndexer && +// !p.IsStatic) +// { +// properties.Add(new PropertyInfo( +// p.Name, +// p.Type.ToDisplayString(), +// GetPropertyTypeKind(p.Type), +// p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableValueType(p.Type))); +// } +// } - // DUPLICATED LOGIC: Same ordering as TypeMetadataBase.GetSerializableProperties() - properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); +// // DUPLICATED LOGIC: Same ordering as TypeMetadataBase.GetSerializableProperties() +// properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); - return new SerializableClassInfo(namespaceName, className, fullTypeName, isStruct, isNestedType, typeNameForSignature, properties); - } +// return new SerializableClassInfo(namespaceName, className, fullTypeName, isStruct, isNestedType, typeNameForSignature, properties); +// } - /// - /// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass") - /// - private static string GetNestedTypeName(INamedTypeSymbol typeSymbol) - { - var parts = new List(); - var current = typeSymbol; +// /// +// /// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass") +// /// +// private static string GetNestedTypeName(INamedTypeSymbol typeSymbol) +// { +// var parts = new List(); +// var current = typeSymbol; - while (current != null) - { - parts.Insert(0, current.Name); - current = current.ContainingType; - } +// while (current != null) +// { +// parts.Insert(0, current.Name); +// current = current.ContainingType; +// } - return string.Join(".", parts); - } +// return string.Join(".", parts); +// } - private static bool IsNullableValueType(ITypeSymbol type) - { - return type is INamedTypeSymbol namedType && - namedType.IsGenericType && - namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T; - } +// private static bool IsNullableValueType(ITypeSymbol type) +// { +// return type is INamedTypeSymbol namedType && +// namedType.IsGenericType && +// namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T; +// } - private static PropertyTypeKind GetPropertyTypeKind(ITypeSymbol type) - { - // Handle nullable value types - if (type is INamedTypeSymbol namedType && namedType.IsGenericType && - namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) - { - // Get underlying type - var underlyingType = namedType.TypeArguments[0]; - return GetPropertyTypeKindForUnderlying(underlyingType, isNullable: true); - } +// private static PropertyTypeKind GetPropertyTypeKind(ITypeSymbol type) +// { +// // Handle nullable value types +// if (type is INamedTypeSymbol namedType && namedType.IsGenericType && +// namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T) +// { +// // Get underlying type +// var underlyingType = namedType.TypeArguments[0]; +// return GetPropertyTypeKindForUnderlying(underlyingType, isNullable: true); +// } - return GetPropertyTypeKindForUnderlying(type, isNullable: false); - } +// return GetPropertyTypeKindForUnderlying(type, isNullable: false); +// } - private static PropertyTypeKind GetPropertyTypeKindForUnderlying(ITypeSymbol type, bool isNullable) - { - switch (type.SpecialType) - { - case SpecialType.System_String: return PropertyTypeKind.String; - case SpecialType.System_Int32: return isNullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32; - case SpecialType.System_Int64: return isNullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64; - case SpecialType.System_Int16: return isNullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16; - case SpecialType.System_Byte: return isNullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte; - case SpecialType.System_SByte: return isNullable ? PropertyTypeKind.NullableSByte : PropertyTypeKind.SByte; - case SpecialType.System_UInt16: return isNullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16; - case SpecialType.System_UInt32: return isNullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32; - case SpecialType.System_UInt64: return isNullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64; - case SpecialType.System_Boolean: return isNullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean; - case SpecialType.System_Single: return isNullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single; - case SpecialType.System_Double: return isNullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double; - case SpecialType.System_Decimal: return isNullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal; - case SpecialType.System_DateTime: return isNullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime; - default: return GetNonSpecialTypeKind(type, isNullable); - } - } +// private static PropertyTypeKind GetPropertyTypeKindForUnderlying(ITypeSymbol type, bool isNullable) +// { +// switch (type.SpecialType) +// { +// case SpecialType.System_String: return PropertyTypeKind.String; +// case SpecialType.System_Int32: return isNullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32; +// case SpecialType.System_Int64: return isNullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64; +// case SpecialType.System_Int16: return isNullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16; +// case SpecialType.System_Byte: return isNullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte; +// case SpecialType.System_SByte: return isNullable ? PropertyTypeKind.NullableSByte : PropertyTypeKind.SByte; +// case SpecialType.System_UInt16: return isNullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16; +// case SpecialType.System_UInt32: return isNullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32; +// case SpecialType.System_UInt64: return isNullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64; +// case SpecialType.System_Boolean: return isNullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean; +// case SpecialType.System_Single: return isNullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single; +// case SpecialType.System_Double: return isNullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double; +// case SpecialType.System_Decimal: return isNullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal; +// case SpecialType.System_DateTime: return isNullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime; +// default: return GetNonSpecialTypeKind(type, isNullable); +// } +// } - private static PropertyTypeKind GetNonSpecialTypeKind(ITypeSymbol type, bool isNullable) - { - var fullName = type.ToDisplayString(); +// private static PropertyTypeKind GetNonSpecialTypeKind(ITypeSymbol type, bool isNullable) +// { +// var fullName = type.ToDisplayString(); - if (fullName == "System.Guid") return isNullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid; - if (fullName == "System.TimeSpan") return isNullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan; - if (fullName == "System.DateTimeOffset") return isNullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset; - if (fullName == "System.DateOnly") return isNullable ? PropertyTypeKind.NullableDateOnly : PropertyTypeKind.DateOnly; - if (fullName == "System.TimeOnly") return isNullable ? PropertyTypeKind.NullableTimeOnly : PropertyTypeKind.TimeOnly; - if (type.TypeKind == TypeKind.Enum) return isNullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum; - if (IsCollectionType(type)) return PropertyTypeKind.Collection; - if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex; +// if (fullName == "System.Guid") return isNullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid; +// if (fullName == "System.TimeSpan") return isNullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan; +// if (fullName == "System.DateTimeOffset") return isNullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset; +// if (fullName == "System.DateOnly") return isNullable ? PropertyTypeKind.NullableDateOnly : PropertyTypeKind.DateOnly; +// if (fullName == "System.TimeOnly") return isNullable ? PropertyTypeKind.NullableTimeOnly : PropertyTypeKind.TimeOnly; +// if (type.TypeKind == TypeKind.Enum) return isNullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum; +// if (IsCollectionType(type)) return PropertyTypeKind.Collection; +// if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex; - return PropertyTypeKind.Unknown; - } +// return PropertyTypeKind.Unknown; +// } - private static bool IsCollectionType(ITypeSymbol type) - { - if (type is IArrayTypeSymbol) - return true; +// private static bool IsCollectionType(ITypeSymbol type) +// { +// if (type is IArrayTypeSymbol) +// return true; - if (type is INamedTypeSymbol namedType) - { - foreach (var iface in namedType.AllInterfaces) - { - if (iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T) - return true; - var ifaceName = iface.ToDisplayString(); - if (ifaceName.StartsWith("System.Collections.Generic.IList<") || - ifaceName.StartsWith("System.Collections.Generic.ICollection<")) - return true; - } - } +// if (type is INamedTypeSymbol namedType) +// { +// foreach (var iface in namedType.AllInterfaces) +// { +// if (iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T) +// return true; +// var ifaceName = iface.ToDisplayString(); +// if (ifaceName.StartsWith("System.Collections.Generic.IList<") || +// ifaceName.StartsWith("System.Collections.Generic.ICollection<")) +// return true; +// } +// } - return false; - } +// return false; +// } - private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) - { - if (classes.IsDefaultOrEmpty) - return; +// private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) +// { +// if (classes.IsDefaultOrEmpty) +// return; - foreach (var classInfo in classes) - { - if (classInfo == null) - continue; +// foreach (var classInfo in classes) +// { +// if (classInfo == null) +// continue; - var source = GenerateSerializerClass(classInfo); - context.AddSource($"{classInfo.ClassName}_AcBinarySerializer.g.cs", SourceText.From(source, Encoding.UTF8)); - } - } +// var source = GenerateSerializerClass(classInfo); +// context.AddSource($"{classInfo.ClassName}_AcBinarySerializer.g.cs", SourceText.From(source, Encoding.UTF8)); +// } +// } - private static string GenerateSerializerClass(SerializableClassInfo classInfo) - { - var sb = new StringBuilder(); +// private static string GenerateSerializerClass(SerializableClassInfo classInfo) +// { +// var sb = new StringBuilder(); - sb.AppendLine("// "); - sb.AppendLine("#nullable enable"); - sb.AppendLine(); - sb.AppendLine("using System;"); - sb.AppendLine("using System.Runtime.CompilerServices;"); - sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); - sb.AppendLine(); +// sb.AppendLine("// "); +// sb.AppendLine("#nullable enable"); +// sb.AppendLine(); +// sb.AppendLine("using System;"); +// sb.AppendLine("using System.Runtime.CompilerServices;"); +// sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); +// sb.AppendLine(); - if (!string.IsNullOrEmpty(classInfo.Namespace)) - { - sb.AppendLine($"namespace {classInfo.Namespace}"); - sb.AppendLine("{"); - } +// if (!string.IsNullOrEmpty(classInfo.Namespace)) +// { +// sb.AppendLine($"namespace {classInfo.Namespace}"); +// sb.AppendLine("{"); +// } - var indent = string.IsNullOrEmpty(classInfo.Namespace) ? "" : " "; +// var indent = string.IsNullOrEmpty(classInfo.Namespace) ? "" : " "; - sb.AppendLine($"{indent}/// "); - sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}."); - sb.AppendLine($"{indent}/// "); - sb.AppendLine($"{indent}internal static class {classInfo.ClassName}_AcBinarySerializer"); - sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent}/// "); +// sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}."); +// sb.AppendLine($"{indent}/// "); +// sb.AppendLine($"{indent}internal static class {classInfo.ClassName}_AcBinarySerializer"); +// sb.AppendLine($"{indent}{{"); - // Generate property count constant - sb.AppendLine($"{indent} public const int PropertyCount = {classInfo.Properties.Count};"); - sb.AppendLine(); +// // Generate property count constant +// sb.AppendLine($"{indent} public const int PropertyCount = {classInfo.Properties.Count};"); +// sb.AppendLine(); - // Generate property names array for validation - sb.AppendLine($"{indent} /// "); - sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical)."); - sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties()."); - sb.AppendLine($"{indent} /// "); - sb.Append($"{indent} public static readonly string[] PropertyNames = new[] {{ "); - sb.Append(string.Join(", ", classInfo.Properties.Select(p => $"\"{p.Name}\""))); - sb.AppendLine(" };"); - sb.AppendLine(); +// // Generate property names array for validation +// sb.AppendLine($"{indent} /// "); +// sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical)."); +// sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties()."); +// sb.AppendLine($"{indent} /// "); +// sb.Append($"{indent} public static readonly string[] PropertyNames = new[] {{ "); +// sb.Append(string.Join(", ", classInfo.Properties.Select(p => $"\"{p.Name}\""))); +// sb.AppendLine(" };"); +// sb.AppendLine(); - // Generate Serialize method - GenerateSerializeMethod(sb, classInfo, indent); +// // Generate Serialize method +// GenerateSerializeMethod(sb, classInfo, indent); - // Generate Deserialize method - GenerateDeserializeMethod(sb, classInfo, indent); +// // Generate Deserialize method +// GenerateDeserializeMethod(sb, classInfo, indent); - sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}}}"); - if (!string.IsNullOrEmpty(classInfo.Namespace)) - { - sb.AppendLine("}"); - } +// if (!string.IsNullOrEmpty(classInfo.Namespace)) +// { +// sb.AppendLine("}"); +// } - return sb.ToString(); - } +// return sb.ToString(); +// } - private static void GenerateSerializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) - { - sb.AppendLine($"{indent} /// "); - sb.AppendLine($"{indent} /// Serializes a {classInfo.ClassName} instance to the binary context."); - sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); - sb.AppendLine($"{indent} /// "); - sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); - sb.AppendLine($"{indent} public static void Serialize({classInfo.TypeNameForSignature} obj, ref AcBinarySerializer.BinarySerializationContext context)"); - sb.AppendLine($"{indent} {{"); +// private static void GenerateSerializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) +// { +// sb.AppendLine($"{indent} /// "); +// sb.AppendLine($"{indent} /// Serializes a {classInfo.ClassName} instance to the binary context."); +// sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); +// sb.AppendLine($"{indent} /// "); +// sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); +// sb.AppendLine($"{indent} public static void Serialize({classInfo.TypeNameForSignature} obj, AcBinarySerializer.BinarySerializationContext context) where TOutput : BinaryOutputBase"); +// sb.AppendLine($"{indent} {{"); +// sb.AppendLine($"{indent} var output = context.Output;"); - foreach (var prop in classInfo.Properties) - { - GenerateSerializeProperty(sb, prop, indent + " "); - } +// foreach (var prop in classInfo.Properties) +// { +// GenerateSerializeProperty(sb, prop, indent + " "); +// } - sb.AppendLine($"{indent} }}"); - sb.AppendLine(); - } +// sb.AppendLine($"{indent} }}"); +// sb.AppendLine(); +// } - private static void GenerateSerializeProperty(StringBuilder sb, PropertyInfo prop, string indent) - { - var propAccess = $"obj.{prop.Name}"; +// private static void GenerateSerializeProperty(StringBuilder sb, PropertyInfo prop, string indent) +// { +// var propAccess = $"obj.{prop.Name}"; - // Handle nullable VALUE types (Nullable) - these use .HasValue and .Value - if (IsNullableValueTypeKind(prop.TypeKind)) - { - sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName} (nullable value type)"); - sb.AppendLine($"{indent}if ({propAccess}.HasValue)"); - sb.AppendLine($"{indent}{{"); - GenerateSerializeValue(sb, prop.TypeKind, $"{propAccess}.Value", indent + " "); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); - sb.AppendLine($"{indent}}}"); - return; - } +// // Handle nullable VALUE types (Nullable) - these use .HasValue and .Value +// if (IsNullableValueTypeKind(prop.TypeKind)) +// { +// sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName} (nullable value type)"); +// sb.AppendLine($"{indent}if ({propAccess}.HasValue)"); +// sb.AppendLine($"{indent}{{"); +// GenerateSerializeValue(sb, prop.TypeKind, $"{propAccess}.Value", indent + " "); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); +// sb.AppendLine($"{indent}}}"); +// return; +// } - sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); +// sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); - // String needs null check - if (prop.TypeKind == PropertyTypeKind.String) - { - sb.AppendLine($"{indent}if ({propAccess} == null)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if ({propAccess}.Length == 0)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.StringEmpty);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.String);"); - sb.AppendLine($"{indent} context.WriteStringUtf8({propAccess});"); - sb.AppendLine($"{indent}}}"); - return; - } +// // String needs null check +// if (prop.TypeKind == PropertyTypeKind.String) +// { +// sb.AppendLine($"{indent}if ({propAccess} == null)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if ({propAccess}.Length == 0)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.StringEmpty);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.String);"); +// sb.AppendLine($"{indent} context.WriteStringUtf8({propAccess});"); +// sb.AppendLine($"{indent}}}"); +// return; +// } - // Nullable reference types (Complex/Collection with ? annotation) - use == null - if (prop.IsNullable && (prop.TypeKind == PropertyTypeKind.Complex || prop.TypeKind == PropertyTypeKind.Collection)) - { - sb.AppendLine($"{indent}if ({propAccess} == null)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent + " "); - sb.AppendLine($"{indent}}}"); - return; - } +// // Nullable reference types (Complex/Collection with ? annotation) - use == null +// if (prop.IsNullable && (prop.TypeKind == PropertyTypeKind.Complex || prop.TypeKind == PropertyTypeKind.Collection)) +// { +// sb.AppendLine($"{indent}if ({propAccess} == null)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent + " "); +// sb.AppendLine($"{indent}}}"); +// return; +// } - GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent); - } +// GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent); +// } - /// - /// Checks if the type kind represents a nullable VALUE type (Nullable<T>), not a reference type - /// - private static bool IsNullableValueTypeKind(PropertyTypeKind kind) - { - return kind >= PropertyTypeKind.NullableInt32 && kind <= PropertyTypeKind.NullableEnum; - } +// /// +// /// Checks if the type kind represents a nullable VALUE type (Nullable<T>), not a reference type +// /// +// private static bool IsNullableValueTypeKind(PropertyTypeKind kind) +// { +// return kind >= PropertyTypeKind.NullableInt32 && kind <= PropertyTypeKind.NullableEnum; +// } - private static void GenerateSerializeValue(StringBuilder sb, PropertyTypeKind typeKind, string valueExpr, string indent) - { - switch (typeKind) - { - case PropertyTypeKind.Int32: - case PropertyTypeKind.NullableInt32: - sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt({valueExpr}, out var tiny_{valueExpr.Replace(".", "_")}))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(tiny_{valueExpr.Replace(".", "_")});"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); - sb.AppendLine($"{indent} context.WriteVarInt({valueExpr});"); - sb.AppendLine($"{indent}}}"); - break; +// private static void GenerateSerializeValue(StringBuilder sb, PropertyTypeKind typeKind, string valueExpr, string indent) +// { +// switch (typeKind) +// { +// case PropertyTypeKind.Int32: +// case PropertyTypeKind.NullableInt32: +// sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt({valueExpr}, out var tiny_{valueExpr.Replace(".", "_")}))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(tiny_{valueExpr.Replace(".", "_")});"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); +// sb.AppendLine($"{indent} context.WriteVarInt({valueExpr});"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Int64: - case PropertyTypeKind.NullableInt64: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Int64);"); - sb.AppendLine($"{indent}context.WriteVarLong({valueExpr});"); - break; +// case PropertyTypeKind.Int64: +// case PropertyTypeKind.NullableInt64: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Int64);"); +// sb.AppendLine($"{indent}context.WriteVarLong({valueExpr});"); +// break; - case PropertyTypeKind.Boolean: - case PropertyTypeKind.NullableBoolean: - sb.AppendLine($"{indent}context.WriteByte({valueExpr} ? BinaryTypeCode.True : BinaryTypeCode.False);"); - break; +// case PropertyTypeKind.Boolean: +// case PropertyTypeKind.NullableBoolean: +// sb.AppendLine($"{indent}context.WriteByte({valueExpr} ? BinaryTypeCode.True : BinaryTypeCode.False);"); +// break; - case PropertyTypeKind.Double: - case PropertyTypeKind.NullableDouble: - sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, {valueExpr});"); - break; +// case PropertyTypeKind.Double: +// case PropertyTypeKind.NullableDouble: +// sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, {valueExpr});"); +// break; - case PropertyTypeKind.Single: - case PropertyTypeKind.NullableSingle: - sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, {valueExpr});"); - break; +// case PropertyTypeKind.Single: +// case PropertyTypeKind.NullableSingle: +// sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, {valueExpr});"); +// break; - case PropertyTypeKind.Decimal: - case PropertyTypeKind.NullableDecimal: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Decimal);"); - sb.AppendLine($"{indent}context.WriteDecimalBits({valueExpr});"); - break; +// case PropertyTypeKind.Decimal: +// case PropertyTypeKind.NullableDecimal: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Decimal);"); +// sb.AppendLine($"{indent}context.WriteDecimalBits({valueExpr});"); +// break; - case PropertyTypeKind.DateTime: - case PropertyTypeKind.NullableDateTime: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTime);"); - sb.AppendLine($"{indent}context.WriteDateTimeBits({valueExpr});"); - break; +// case PropertyTypeKind.DateTime: +// case PropertyTypeKind.NullableDateTime: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTime);"); +// sb.AppendLine($"{indent}context.WriteDateTimeBits({valueExpr});"); +// break; - case PropertyTypeKind.Guid: - case PropertyTypeKind.NullableGuid: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Guid);"); - sb.AppendLine($"{indent}context.WriteGuidBits({valueExpr});"); - break; +// case PropertyTypeKind.Guid: +// case PropertyTypeKind.NullableGuid: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Guid);"); +// sb.AppendLine($"{indent}context.WriteGuidBits({valueExpr});"); +// break; - case PropertyTypeKind.Byte: - case PropertyTypeKind.NullableByte: - sb.AppendLine($"{indent}context.WriteTwoBytes(BinaryTypeCode.UInt8, {valueExpr});"); - break; +// case PropertyTypeKind.Byte: +// case PropertyTypeKind.NullableByte: +// sb.AppendLine($"{indent}context.WriteTwoBytes(BinaryTypeCode.UInt8, {valueExpr});"); +// break; - case PropertyTypeKind.Int16: - case PropertyTypeKind.NullableInt16: - sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, {valueExpr});"); - break; +// case PropertyTypeKind.Int16: +// case PropertyTypeKind.NullableInt16: +// sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, {valueExpr});"); +// break; - case PropertyTypeKind.UInt16: - case PropertyTypeKind.NullableUInt16: - sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, {valueExpr});"); - break; +// case PropertyTypeKind.UInt16: +// case PropertyTypeKind.NullableUInt16: +// sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, {valueExpr});"); +// break; - case PropertyTypeKind.UInt32: - case PropertyTypeKind.NullableUInt32: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); - sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); - sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); - break; +// case PropertyTypeKind.UInt32: +// case PropertyTypeKind.NullableUInt32: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); +// sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);"); +// sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});"); +// break; - case PropertyTypeKind.UInt64: - case PropertyTypeKind.NullableUInt64: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt64);"); - sb.AppendLine($"{indent}context.WriteVarULong({valueExpr});"); - break; +// case PropertyTypeKind.UInt64: +// case PropertyTypeKind.NullableUInt64: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt64);"); +// sb.AppendLine($"{indent}context.WriteVarULong({valueExpr});"); +// break; - case PropertyTypeKind.TimeSpan: - case PropertyTypeKind.NullableTimeSpan: - sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, {valueExpr}.Ticks);"); - break; +// case PropertyTypeKind.TimeSpan: +// case PropertyTypeKind.NullableTimeSpan: +// sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, {valueExpr}.Ticks);"); +// break; - case PropertyTypeKind.DateTimeOffset: - case PropertyTypeKind.NullableDateTimeOffset: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTimeOffset);"); - sb.AppendLine($"{indent}context.WriteDateTimeOffsetBits({valueExpr});"); - break; +// case PropertyTypeKind.DateTimeOffset: +// case PropertyTypeKind.NullableDateTimeOffset: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTimeOffset);"); +// sb.AppendLine($"{indent}context.WriteDateTimeOffsetBits({valueExpr});"); +// break; - case PropertyTypeKind.Enum: - case PropertyTypeKind.NullableEnum: - sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Enum);"); - sb.AppendLine($"{indent}var enumVal = (int){valueExpr};"); - sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(tinyEnum);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); - sb.AppendLine($"{indent} context.WriteVarInt(enumVal);"); - sb.AppendLine($"{indent}}}"); - break; +// case PropertyTypeKind.Enum: +// case PropertyTypeKind.NullableEnum: +// sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Enum);"); +// sb.AppendLine($"{indent}var enumVal = (int){valueExpr};"); +// sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(tinyEnum);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);"); +// sb.AppendLine($"{indent} context.WriteVarInt(enumVal);"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Collection: - case PropertyTypeKind.Complex: - // TODO: Collections and complex types will be implemented later - sb.AppendLine($"{indent}// TODO: Complex/Collection type - fallback to runtime serializer"); - sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection types not yet implemented in generated serializer\");"); - break; +// case PropertyTypeKind.Collection: +// case PropertyTypeKind.Complex: +// // TODO: Collections and complex types will be implemented later +// sb.AppendLine($"{indent}// TODO: Complex/Collection type - fallback to runtime serializer"); +// sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection types not yet implemented in generated serializer\");"); +// break; - default: - sb.AppendLine($"{indent}// Unknown type - fallback needed"); - sb.AppendLine($"{indent}throw new NotImplementedException($\"Type {typeKind} not implemented in generated serializer\");"); - break; - } - } +// default: +// sb.AppendLine($"{indent}// Unknown type - fallback needed"); +// sb.AppendLine($"{indent}throw new NotImplementedException($\"Type {typeKind} not implemented in generated serializer\");"); +// break; +// } +// } - private static void GenerateDeserializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) - { - sb.AppendLine($"{indent} /// "); - sb.AppendLine($"{indent} /// Deserializes properties into a {classInfo.ClassName} instance from the binary context."); - sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); - sb.AppendLine($"{indent} /// "); - sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); - sb.AppendLine($"{indent} public static void Deserialize({classInfo.TypeNameForSignature} obj, ref AcBinaryDeserializer.BinaryDeserializationContext context)"); - sb.AppendLine($"{indent} {{"); +// private static void GenerateDeserializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent) +// { +// sb.AppendLine($"{indent} /// "); +// sb.AppendLine($"{indent} /// Deserializes properties into a {classInfo.ClassName} instance from the binary context."); +// sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives."); +// sb.AppendLine($"{indent} /// "); +// sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]"); +// sb.AppendLine($"{indent} public static void Deserialize({classInfo.TypeNameForSignature} obj, ref AcBinaryDeserializer.BinaryDeserializationContext context)"); +// sb.AppendLine($"{indent} {{"); - foreach (var prop in classInfo.Properties) - { - GenerateDeserializeProperty(sb, prop, indent + " "); - } +// foreach (var prop in classInfo.Properties) +// { +// GenerateDeserializeProperty(sb, prop, indent + " "); +// } - sb.AppendLine($"{indent} }}"); - } +// sb.AppendLine($"{indent} }}"); +// } - private static void GenerateDeserializeProperty(StringBuilder sb, PropertyInfo prop, string indent) - { - sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} var peekCode = context.PeekByte();"); +// private static void GenerateDeserializeProperty(StringBuilder sb, PropertyInfo prop, string indent) +// { +// sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} var peekCode = context.PeekByte();"); - // Handle Skip marker - sb.AppendLine($"{indent} if (peekCode == BinaryTypeCode.PropertySkip)"); - sb.AppendLine($"{indent} {{"); - sb.AppendLine($"{indent} context.ReadByte(); // consume Skip marker"); - sb.AppendLine($"{indent} // Property keeps default value"); - sb.AppendLine($"{indent} }}"); +// // Handle Skip marker +// sb.AppendLine($"{indent} if (peekCode == BinaryTypeCode.PropertySkip)"); +// sb.AppendLine($"{indent} {{"); +// sb.AppendLine($"{indent} context.ReadByte(); // consume Skip marker"); +// sb.AppendLine($"{indent} // Property keeps default value"); +// sb.AppendLine($"{indent} }}"); - // Handle Null marker - sb.AppendLine($"{indent} else if (peekCode == BinaryTypeCode.Null)"); - sb.AppendLine($"{indent} {{"); - sb.AppendLine($"{indent} context.ReadByte(); // consume Null marker"); - if (prop.IsNullable || prop.TypeKind == PropertyTypeKind.String) - { - sb.AppendLine($"{indent} obj.{prop.Name} = default;"); - } - else - { - sb.AppendLine($"{indent} // Non-nullable property, keep default"); - } - sb.AppendLine($"{indent} }}"); +// // Handle Null marker +// sb.AppendLine($"{indent} else if (peekCode == BinaryTypeCode.Null)"); +// sb.AppendLine($"{indent} {{"); +// sb.AppendLine($"{indent} context.ReadByte(); // consume Null marker"); +// if (prop.IsNullable || prop.TypeKind == PropertyTypeKind.String) +// { +// sb.AppendLine($"{indent} obj.{prop.Name} = default;"); +// } +// else +// { +// sb.AppendLine($"{indent} // Non-nullable property, keep default"); +// } +// sb.AppendLine($"{indent} }}"); - // Handle actual value - sb.AppendLine($"{indent} else"); - sb.AppendLine($"{indent} {{"); - GenerateDeserializeValue(sb, prop, indent + " "); - sb.AppendLine($"{indent} }}"); +// // Handle actual value +// sb.AppendLine($"{indent} else"); +// sb.AppendLine($"{indent} {{"); +// GenerateDeserializeValue(sb, prop, indent + " "); +// sb.AppendLine($"{indent} }}"); - sb.AppendLine($"{indent}}}"); - } +// sb.AppendLine($"{indent}}}"); +// } - private static void GenerateDeserializeValue(StringBuilder sb, PropertyInfo prop, string indent) - { - var propAccess = $"obj.{prop.Name}"; +// private static void GenerateDeserializeValue(StringBuilder sb, PropertyInfo prop, string indent) +// { +// var propAccess = $"obj.{prop.Name}"; - switch (prop.TypeKind) - { - case PropertyTypeKind.String: - sb.AppendLine($"{indent}if (peekCode == BinaryTypeCode.StringEmpty)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = string.Empty;"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.String)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} var len = (int)context.ReadVarUInt();"); - sb.AppendLine($"{indent} {propAccess} = context.ReadStringUtf8(len);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if (BinaryTypeCode.IsFixStr(peekCode))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} var len = BinaryTypeCode.DecodeFixStrLength(peekCode);"); - sb.AppendLine($"{indent} {propAccess} = len == 0 ? string.Empty : context.ReadStringUtf8(len);"); - sb.AppendLine($"{indent}}}"); - break; +// switch (prop.TypeKind) +// { +// case PropertyTypeKind.String: +// sb.AppendLine($"{indent}if (peekCode == BinaryTypeCode.StringEmpty)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = string.Empty;"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.String)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} var len = (int)context.ReadVarUInt();"); +// sb.AppendLine($"{indent} {propAccess} = context.ReadStringUtf8(len);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if (BinaryTypeCode.IsFixStr(peekCode))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} var len = BinaryTypeCode.DecodeFixStrLength(peekCode);"); +// sb.AppendLine($"{indent} {propAccess} = len == 0 ? string.Empty : context.ReadStringUtf8(len);"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Int32: - case PropertyTypeKind.NullableInt32: - sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); - sb.AppendLine($"{indent}}}"); - break; +// case PropertyTypeKind.Int32: +// case PropertyTypeKind.NullableInt32: +// sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Int64: - case PropertyTypeKind.NullableInt64: - sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int64)"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = context.ReadVarLong();"); - sb.AppendLine($"{indent}}}"); - break; +// case PropertyTypeKind.Int64: +// case PropertyTypeKind.NullableInt64: +// sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int64)"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = context.ReadVarLong();"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Boolean: - case PropertyTypeKind.NullableBoolean: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = peekCode == BinaryTypeCode.True;"); - break; +// case PropertyTypeKind.Boolean: +// case PropertyTypeKind.NullableBoolean: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = peekCode == BinaryTypeCode.True;"); +// break; - case PropertyTypeKind.Double: - case PropertyTypeKind.NullableDouble: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadDoubleUnsafe();"); - break; +// case PropertyTypeKind.Double: +// case PropertyTypeKind.NullableDouble: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadDoubleUnsafe();"); +// break; - case PropertyTypeKind.Single: - case PropertyTypeKind.NullableSingle: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadSingleUnsafe();"); - break; +// case PropertyTypeKind.Single: +// case PropertyTypeKind.NullableSingle: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadSingleUnsafe();"); +// break; - case PropertyTypeKind.Decimal: - case PropertyTypeKind.NullableDecimal: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadDecimalUnsafe();"); - break; +// case PropertyTypeKind.Decimal: +// case PropertyTypeKind.NullableDecimal: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadDecimalUnsafe();"); +// break; - case PropertyTypeKind.DateTime: - case PropertyTypeKind.NullableDateTime: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeUnsafe();"); - break; +// case PropertyTypeKind.DateTime: +// case PropertyTypeKind.NullableDateTime: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeUnsafe();"); +// break; - case PropertyTypeKind.Guid: - case PropertyTypeKind.NullableGuid: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadGuidUnsafe();"); - break; +// case PropertyTypeKind.Guid: +// case PropertyTypeKind.NullableGuid: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadGuidUnsafe();"); +// break; - case PropertyTypeKind.Byte: - case PropertyTypeKind.NullableByte: - sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = (byte)BinaryTypeCode.DecodeTinyInt(peekCode);"); - sb.AppendLine($"{indent}}}"); - sb.AppendLine($"{indent}else"); - sb.AppendLine($"{indent}{{"); - sb.AppendLine($"{indent} context.ReadByte();"); - sb.AppendLine($"{indent} {propAccess} = context.ReadByte();"); - sb.AppendLine($"{indent}}}"); - break; +// case PropertyTypeKind.Byte: +// case PropertyTypeKind.NullableByte: +// sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = (byte)BinaryTypeCode.DecodeTinyInt(peekCode);"); +// sb.AppendLine($"{indent}}}"); +// sb.AppendLine($"{indent}else"); +// sb.AppendLine($"{indent}{{"); +// sb.AppendLine($"{indent} context.ReadByte();"); +// sb.AppendLine($"{indent} {propAccess} = context.ReadByte();"); +// sb.AppendLine($"{indent}}}"); +// break; - case PropertyTypeKind.Int16: - case PropertyTypeKind.NullableInt16: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadInt16Unsafe();"); - break; +// case PropertyTypeKind.Int16: +// case PropertyTypeKind.NullableInt16: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadInt16Unsafe();"); +// break; - case PropertyTypeKind.UInt16: - case PropertyTypeKind.NullableUInt16: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadUInt16Unsafe();"); - break; +// case PropertyTypeKind.UInt16: +// case PropertyTypeKind.NullableUInt16: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadUInt16Unsafe();"); +// break; - case PropertyTypeKind.UInt32: - case PropertyTypeKind.NullableUInt32: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadVarUInt();"); - break; +// case PropertyTypeKind.UInt32: +// case PropertyTypeKind.NullableUInt32: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadVarUInt();"); +// break; - case PropertyTypeKind.UInt64: - case PropertyTypeKind.NullableUInt64: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadVarULong();"); - break; +// case PropertyTypeKind.UInt64: +// case PropertyTypeKind.NullableUInt64: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadVarULong();"); +// break; - case PropertyTypeKind.TimeSpan: - case PropertyTypeKind.NullableTimeSpan: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadTimeSpanUnsafe();"); - break; +// case PropertyTypeKind.TimeSpan: +// case PropertyTypeKind.NullableTimeSpan: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadTimeSpanUnsafe();"); +// break; - case PropertyTypeKind.DateTimeOffset: - case PropertyTypeKind.NullableDateTimeOffset: - sb.AppendLine($"{indent}context.ReadByte();"); - sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeOffsetUnsafe();"); - break; +// case PropertyTypeKind.DateTimeOffset: +// case PropertyTypeKind.NullableDateTimeOffset: +// sb.AppendLine($"{indent}context.ReadByte();"); +// sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeOffsetUnsafe();"); +// break; - case PropertyTypeKind.Enum: - case PropertyTypeKind.NullableEnum: - sb.AppendLine($"{indent}// TODO: Enum deserialization needs type info"); - sb.AppendLine($"{indent}throw new NotImplementedException(\"Enum deserialization not yet implemented\");"); - break; +// case PropertyTypeKind.Enum: +// case PropertyTypeKind.NullableEnum: +// sb.AppendLine($"{indent}// TODO: Enum deserialization needs type info"); +// sb.AppendLine($"{indent}throw new NotImplementedException(\"Enum deserialization not yet implemented\");"); +// break; - case PropertyTypeKind.Collection: - case PropertyTypeKind.Complex: - sb.AppendLine($"{indent}// TODO: Complex/Collection types"); - sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection deserialization not yet implemented\");"); - break; +// case PropertyTypeKind.Collection: +// case PropertyTypeKind.Complex: +// sb.AppendLine($"{indent}// TODO: Complex/Collection types"); +// sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection deserialization not yet implemented\");"); +// break; - default: - sb.AppendLine($"{indent}throw new NotImplementedException($\"Type deserialization not implemented\");"); - break; - } - } -} +// default: +// sb.AppendLine($"{indent}throw new NotImplementedException($\"Type deserialization not implemented\");"); +// break; +// } +// } +//} -/// -/// Information about a class marked with [AcBinarySerializable]. -/// -internal sealed class SerializableClassInfo -{ - public string Namespace { get; } - public string ClassName { get; } - public string FullTypeName { get; } - public bool IsStruct { get; } - public bool IsNestedType { get; } - /// - /// The type name to use in method signatures. For nested types this includes the containing types. - /// e.g., "OuterClass.InnerClass" - /// - public string TypeNameForSignature { get; } - public List Properties { get; } +///// +///// Information about a class marked with [AcBinarySerializable]. +///// +//internal sealed class SerializableClassInfo +//{ +// public string Namespace { get; } +// public string ClassName { get; } +// public string FullTypeName { get; } +// public bool IsStruct { get; } +// public bool IsNestedType { get; } +// /// +// /// The type name to use in method signatures. For nested types this includes the containing types. +// /// e.g., "OuterClass.InnerClass" +// /// +// public string TypeNameForSignature { get; } +// public List Properties { get; } - public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List properties) - { - Namespace = ns; - ClassName = className; - FullTypeName = fullTypeName; - IsStruct = isStruct; - IsNestedType = isNestedType; - TypeNameForSignature = typeNameForSignature; - Properties = properties; - } -} +// public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List properties) +// { +// Namespace = ns; +// ClassName = className; +// FullTypeName = fullTypeName; +// IsStruct = isStruct; +// IsNestedType = isNestedType; +// TypeNameForSignature = typeNameForSignature; +// Properties = properties; +// } +//} -/// -/// Information about a serializable property. -/// -internal sealed class PropertyInfo -{ - public string Name { get; } - public string TypeName { get; } - public PropertyTypeKind TypeKind { get; } - public bool IsNullable { get; } +///// +///// Information about a serializable property. +///// +//internal sealed class PropertyInfo +//{ +// public string Name { get; } +// public string TypeName { get; } +// public PropertyTypeKind TypeKind { get; } +// public bool IsNullable { get; } - public PropertyInfo(string name, string typeName, PropertyTypeKind typeKind, bool isNullable) - { - Name = name; - TypeName = typeName; - TypeKind = typeKind; - IsNullable = isNullable; - } -} +// public PropertyInfo(string name, string typeName, PropertyTypeKind typeKind, bool isNullable) +// { +// Name = name; +// TypeName = typeName; +// TypeKind = typeKind; +// IsNullable = isNullable; +// } +//} -/// -/// Kind of property type for code generation. -/// -internal enum PropertyTypeKind -{ - Unknown, - String, - Int32, - Int64, - Int16, - Byte, - SByte, - UInt16, - UInt32, - UInt64, - Boolean, - Single, - Double, - Decimal, - DateTime, - DateTimeOffset, - TimeSpan, - DateOnly, - TimeOnly, - Guid, - Enum, - Collection, - Complex, - // Nullable variants - NullableInt32, - NullableInt64, - NullableInt16, - NullableByte, - NullableSByte, - NullableUInt16, - NullableUInt32, - NullableUInt64, - NullableBoolean, - NullableSingle, - NullableDouble, - NullableDecimal, - NullableDateTime, - NullableDateTimeOffset, - NullableTimeSpan, - NullableDateOnly, - NullableTimeOnly, - NullableGuid, - NullableEnum -} +///// +///// Kind of property type for code generation. +///// +//internal enum PropertyTypeKind +//{ +// Unknown, +// String, +// Int32, +// Int64, +// Int16, +// Byte, +// SByte, +// UInt16, +// UInt32, +// UInt64, +// Boolean, +// Single, +// Double, +// Decimal, +// DateTime, +// DateTimeOffset, +// TimeSpan, +// DateOnly, +// TimeOnly, +// Guid, +// Enum, +// Collection, +// Complex, +// // Nullable variants +// NullableInt32, +// NullableInt64, +// NullableInt16, +// NullableByte, +// NullableSByte, +// NullableUInt16, +// NullableUInt32, +// NullableUInt64, +// NullableBoolean, +// NullableSingle, +// NullableDouble, +// NullableDecimal, +// NullableDateTime, +// NullableDateTimeOffset, +// NullableTimeSpan, +// NullableDateOnly, +// NullableTimeOnly, +// NullableGuid, +// NullableEnum +//} diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs index 746c28c..92b524c 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs @@ -41,7 +41,7 @@ public static partial class AcBinaryDeserializer } // Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute - if (type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false)) + if (false && type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false)) { GeneratedSerializerType = FindGeneratedSerializerType(type); HasGeneratedDeserializer = GeneratedSerializerType != null; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 400ad83..3a5cccd 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -2,11 +2,7 @@ using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Text; using System.Threading; using static AyCode.Core.Helpers.JsonUtilities; @@ -14,12 +10,12 @@ namespace AyCode.Core.Serializers.Binaries; public static partial class AcBinarySerializer { - private static class BinarySerializationContextPool + private static class BinarySerializationContextPool where TOutput : BinaryOutputBase { - private static readonly ConcurrentQueue Pool = new(); + private static readonly ConcurrentQueue> Pool = new(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BinarySerializationContext Get(AcBinarySerializerOptions options) + public static BinarySerializationContext Get(AcBinarySerializerOptions options) { if (Pool.TryDequeue(out var context)) { @@ -27,17 +23,17 @@ public static partial class AcBinarySerializer return context; } - return new BinarySerializationContext(options); + return new BinarySerializationContext(options); } - public static void ReturnAsync(BinarySerializationContext context) + public static void ReturnAsync(BinarySerializationContext context) { // 🔥 FIRE-AND-FORGET: cleanup háttérben ThreadPool.UnsafeQueueUserWorkItem(Return, context, preferLocal: true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(BinarySerializationContext context) + public static void Return(BinarySerializationContext context) { if (Pool.Count < context.Options.MaxContextPoolSize) { @@ -51,50 +47,23 @@ public static partial class AcBinarySerializer } } - public static int GrowBufferCount => -#if DEBUG - BinarySerializationContext.GrowBufferCount; -#else - -1; -#endif - - public static long GrowBufferTotalBytes => -#if DEBUG - BinarySerializationContext.GrowBufferTotalBytes; -#else - -1; -#endif - /// - /// Binary serialization context. Public for generated serializers. + /// Binary serialization context. Generic on TOutput for JIT devirtualization. + /// TOutput is the binary output target (ArrayBinaryOutput for byte[], BufferWriterBinaryOutput for IBufferWriter). + /// All write operations are delegated to Output — the context only manages serialization state + /// (string interning, reference tracking, metadata, property filtering). /// - internal sealed class BinarySerializationContext : SerializationContextBase, IDisposable + internal sealed class BinarySerializationContext + : SerializationContextBase, IDisposable + where TOutput : BinaryOutputBase { - private const int MinBufferSize = 512; - private const int BufferHalvingThreshold = 4; // Halve buffer when > _initialBufferSize * this private const int PropertyIndexBufferMaxCache = 512; private const int PropertyStateBufferMaxCache = 512; - private const int InitialInternCapacity = 32; - private byte[] _buffer; - private int _position; - private int _initialBufferSize; - -#if DEBUG - /// - /// Counts how many times GrowBuffer was called during serialization. - /// Used for benchmarking buffer allocation efficiency. - /// - public static int GrowBufferCount { get; set; } /// - /// Total bytes allocated by GrowBuffer during serialization. - /// Used for benchmarking buffer allocation efficiency. + /// Strongly-typed output target for JIT devirtualization in the write pass. /// - public static long GrowBufferTotalBytes { get; set; } -#endif - - // Use shared reference tracker from AcSerializerCommon - //private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new(); + public TOutput Output = default!; private IdentityMap? _stringInternMap; private int _nextCacheIndex; // Next dense cache index to assign (starts at 0, uses ++_nextCacheIndex) @@ -160,12 +129,17 @@ public static partial class AcBinarySerializer /// public bool HasPropertyFilter { get; private set; } - public int Position => _position; + /// + /// Current output position (delegates to Output). + /// + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Output.Position; + } public BinarySerializationContext(AcBinarySerializerOptions options) { - _initialBufferSize = Math.Max(options.InitialBufferCapacity, MinBufferSize); - _buffer = ArrayPool.Shared.Rent(_initialBufferSize); Reset(options); } @@ -179,26 +153,11 @@ public static partial class AcBinarySerializer { // IMPORTANT: base.Reset sets Options first, so derived code can use Options-derived properties base.Reset(options); - - _position = 0; - _initialBufferSize = Math.Max(Options.InitialBufferCapacity, MinBufferSize); HasPropertyFilter = Options.PropertyFilter != null; - - // NOTE: GrowBufferCount és GrowBufferTotalBytes NEM nullázódik itt! - // Kumulatívan gyűjtjük a benchmark során. - - if (_buffer.Length < _initialBufferSize || _buffer.Length > _initialBufferSize * BufferHalvingThreshold) - { - ArrayPool.Shared.Return(_buffer); - _buffer = ArrayPool.Shared.Rent(_initialBufferSize); - } } public override void Clear() { - _position = 0; - - //_refTracker.Reset(); _stringInternMap?.Reset(); _nextCacheIndex = 0; _nextFirstIndex = 0; @@ -215,27 +174,12 @@ public static partial class AcBinarySerializer _propertyStateBuffer = null; } - // Halve oversized output buffer (IdentityMap pattern: gradual shrink after spike) - if (_buffer.Length > _initialBufferSize * BufferHalvingThreshold) - { - var nextSize = Math.Max(_buffer.Length / 2, _initialBufferSize); - ArrayPool.Shared.Return(_buffer); - _buffer = ArrayPool.Shared.Rent(nextSize); - } - // Clear wrapper tracking - returns IdentityMap arrays to pool base.Clear(); } - public void Dispose() { - if (_buffer != null) - { - ArrayPool.Shared.Return(_buffer); - _buffer = null!; - } - if (_propertyIndexBuffer != null) { ArrayPool.Shared.Return(_propertyIndexBuffer); @@ -248,6 +192,9 @@ public static partial class AcBinarySerializer _propertyStateBuffer = null; } + // Dispose the output if it implements IDisposable (e.g. ArrayBinaryOutput returns buffer to pool) + if (Output is IDisposable disposableOutput) + disposableOutput.Dispose(); } #region String Interning @@ -262,7 +209,7 @@ public static partial class AcBinarySerializer if (_stringInternMap == null) { found = false; - return ref System.Runtime.CompilerServices.Unsafe.NullRef(); + return ref Unsafe.NullRef(); } if (_stringInternMap.TryAdd(value, out var slotIndex)) @@ -312,7 +259,6 @@ public static partial class AcBinarySerializer /// public int GetCacheCount() => _nextCacheIndex; - #endregion #region UseMetadata Type Tracking @@ -338,17 +284,17 @@ public static partial class AcBinarySerializer /// Ismételt: [propNameHash (4b)] /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteInlineMetadata(BinarySerializeTypeMetadata metadata, bool isFirstOccurrence) + public void WriteInlineMetadata(BinarySerializeTypeMetadata metadata, TOutput output, bool isFirstOccurrence) { - WriteRaw(metadata.PropNameHash); + output.WriteRaw(metadata.PropNameHash); if (isFirstOccurrence) { var hashes = metadata.MetadataPropertyHashes; - WriteVarUInt((uint)hashes.Length); + output.WriteVarUInt((uint)hashes.Length); for (var i = 0; i < hashes.Length; i++) { - WriteRaw(hashes[i]); + output.WriteRaw(hashes[i]); } } } @@ -382,42 +328,6 @@ public static partial class AcBinarySerializer #endregion - #region Output - - /// - /// Returns the serialized data as a ReadOnlySpan without allocation. - /// Use this for compression or other processing before final ToArray(). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan AsSpan() => _buffer.AsSpan(0, _position); - - public byte[] ToArray() - { - var result = GC.AllocateUninitializedArray(_position); - _buffer.AsSpan(0, _position).CopyTo(result); - return result; - } - - public void WriteTo(IBufferWriter writer) - { - var span = writer.GetSpan(_position); - _buffer.AsSpan(0, _position).CopyTo(span); - writer.Advance(_position); - } - - public BinarySerializationResult DetachResult() - { - var resultBuffer = _buffer; - var resultLength = _position; - - _buffer = ArrayPool.Shared.Rent(_initialBufferSize); - _position = 0; - - return new BinarySerializationResult(resultBuffer, resultLength, pooled: true); - } - - #endregion - #region Property Filtering [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -441,440 +351,8 @@ public static partial class AcBinarySerializer #endregion - #region Buffer Helpers - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureCapacity(int additionalBytes) - { - var required = _position + additionalBytes; - if (required <= _buffer.Length) - { - return; - } - - GrowBuffer(required); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowBuffer(int required) - { - var newSize = Math.Max(_buffer.Length * 2, required); - var newBuffer = ArrayPool.Shared.Rent(newSize); - _buffer.AsSpan(0, _position).CopyTo(newBuffer); - ArrayPool.Shared.Return(_buffer); - _buffer = newBuffer; - - #if DEBUG - GrowBufferCount++; - GrowBufferTotalBytes += newSize; - #endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteByte(byte value) - { - if (_position >= _buffer.Length) - { - GrowBuffer(_position + 1); - } - _buffer[_position++] = value; - } - - /// - /// Write type code byte followed by a raw value. Batches EnsureCapacity call. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteTypeCodeAndRaw(byte typeCode, T value) where T : unmanaged - { - var size = 1 + Unsafe.SizeOf(); - EnsureCapacity(size); - _buffer[_position++] = typeCode; - Unsafe.WriteUnaligned(ref _buffer[_position], value); - _position += Unsafe.SizeOf(); - } - - /// - /// Write two bytes efficiently. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteTwoBytes(byte b1, byte b2) - { - EnsureCapacity(2); - _buffer[_position++] = b1; - _buffer[_position++] = b2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBytes(ReadOnlySpan data) - { - EnsureCapacity(data.Length); - data.CopyTo(_buffer.AsSpan(_position)); - _position += data.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteRaw(T value) where T : unmanaged - { - var size = Unsafe.SizeOf(); - EnsureCapacity(size); - Unsafe.WriteUnaligned(ref _buffer[_position], value); - _position += size; - } - - #endregion - - #region Specialized Writers - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDecimalBits(decimal value) - { - EnsureCapacity(16); - Span bits = stackalloc int[4]; - decimal.TryGetBits(value, bits, out _); - MemoryMarshal.AsBytes(bits).CopyTo(_buffer.AsSpan(_position, 16)); - _position += 16; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDateTimeBits(DateTime value) - { - EnsureCapacity(9); - Unsafe.WriteUnaligned(ref _buffer[_position], value.Ticks); - _buffer[_position + 8] = (byte)value.Kind; - _position += 9; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteGuidBits(Guid value) - { - EnsureCapacity(16); - value.TryWriteBytes(_buffer.AsSpan(_position, 16)); - _position += 16; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDateTimeOffsetBits(DateTimeOffset value) - { - EnsureCapacity(10); - Unsafe.WriteUnaligned(ref _buffer[_position], value.UtcTicks); - Unsafe.WriteUnaligned(ref _buffer[_position + 8], (short)value.Offset.TotalMinutes); - _position += 10; - } - - - public void WriteVarInt(int value) - { - var encoded = (uint)((value << 1) ^ (value >> 31)); - // Fast path for small positive values (0-63 when ZigZag encoded) - if (encoded < 0x80) - { - EnsureCapacity(1); - _buffer[_position++] = (byte)encoded; - return; - } - EnsureCapacity(5); - WriteVarUIntInternal(encoded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVarUInt(uint value) - { - // Fast path for small values (0-127) - if (value < 0x80) - { - EnsureCapacity(1); - _buffer[_position++] = (byte)value; - return; - } - EnsureCapacity(5); - WriteVarUIntInternal(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteVarUIntInternal(uint value) - { - while (value >= 0x80) - { - _buffer[_position++] = (byte)(value | 0x80); - value >>= 7; - } - - _buffer[_position++] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVarLong(long value) - { - var encoded = (ulong)((value << 1) ^ (value >> 63)); - // Fast path for small values - if (encoded < 0x80) - { - EnsureCapacity(1); - _buffer[_position++] = (byte)encoded; - return; - } - EnsureCapacity(10); - WriteVarULongInternal(encoded); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteVarULong(ulong value) - { - // Fast path for small values (0-127) - if (value < 0x80) - { - EnsureCapacity(1); - _buffer[_position++] = (byte)value; - return; - } - EnsureCapacity(10); - WriteVarULongInternal(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteVarULongInternal(ulong value) - { - while (value >= 0x80) - { - _buffer[_position++] = (byte)(value | 0x80); - value >>= 7; - } - - _buffer[_position++] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteStringUtf8(string value) - { - // Fast path for ASCII-only strings using SIMD-optimized check - if (Ascii.IsValid(value)) - { - WriteVarUInt((uint)value.Length); - EnsureCapacity(value.Length); - // Use System.Text.Ascii for SIMD-optimized ASCII to bytes conversion - Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, value.Length), out _); - _position += value.Length; - return; - } - - // Standard path for multi-byte UTF8 - var byteCount = Utf8NoBom.GetByteCount(value); - WriteVarUInt((uint)byteCount); - EnsureCapacity(byteCount); - Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); - _position += byteCount; - } - - /// - /// Checks if string contains only ASCII characters (0-127). - /// Optimized loop with early exit. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAscii(string value) - { - var span = value.AsSpan(); - for (var i = 0; i < span.Length; i++) - { - if (span[i] > 127) - return false; - } - return true; - } - - /// - /// Writes ASCII string directly to byte buffer (char to byte, no encoding needed). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteAsciiDirect(ReadOnlySpan source, Span destination) - { - for (var i = 0; i < source.Length; i++) - { - destination[i] = (byte)source[i]; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WritePreencodedPropertyName(ReadOnlySpan utf8Name) - { - WriteByte(BinaryTypeCode.String); - WriteVarUInt((uint)utf8Name.Length); - WriteBytes(utf8Name); - } - - public void WriteInt32ArrayOptimized(int[] array) - { - for (var i = 0; i < array.Length; i++) - { - var value = array[i]; - if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny)) - { - WriteByte(tiny); - } - else - { - WriteByte(BinaryTypeCode.Int32); - WriteVarInt(value); - } - } - } - - public void WriteLongArrayOptimized(long[] array) - { - for (var i = 0; i < array.Length; i++) - { - var value = array[i]; - if (value >= int.MinValue && value <= int.MaxValue) - { - var intValue = (int)value; - if (BinaryTypeCode.TryEncodeTinyInt(intValue, out var tiny)) - { - WriteByte(tiny); - } - else - { - WriteByte(BinaryTypeCode.Int32); - WriteVarInt(intValue); - } - } - else - { - WriteByte(BinaryTypeCode.Int64); - WriteVarLong(value); - } - } - } - - public void WriteDoubleArrayBulk(double[] array) - { - EnsureCapacity(array.Length * 9); - for (var i = 0; i < array.Length; i++) - { - _buffer[_position++] = BinaryTypeCode.Float64; - Unsafe.WriteUnaligned(ref _buffer[_position], array[i]); - _position += 8; - } - } - - public void WriteFloatArrayBulk(float[] array) - { - EnsureCapacity(array.Length * 5); - for (var i = 0; i < array.Length; i++) - { - _buffer[_position++] = BinaryTypeCode.Float32; - Unsafe.WriteUnaligned(ref _buffer[_position], array[i]); - _position += 4; - } - } - - public void WriteGuidArrayBulk(Guid[] array) - { - EnsureCapacity(array.Length * 17); - for (var i = 0; i < array.Length; i++) - { - _buffer[_position++] = BinaryTypeCode.Guid; - array[i].TryWriteBytes(_buffer.AsSpan(_position, 16)); - _position += 16; - } - } - - #endregion - - #region SIMD Bulk Copy - - /// - /// Copy bytes using SIMD when available, otherwise fall back to standard copy. - /// Optimized for Blazor WASM where Vector operations are supported. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBytesSimd(ReadOnlySpan source) - { - EnsureCapacity(source.Length); - var destination = _buffer.AsSpan(_position, source.Length); - - if (Vector.IsHardwareAccelerated && source.Length >= Vector.Count * 2) - { - CopyWithSimd(source, destination); - } - else - { - source.CopyTo(destination); - } - - _position += source.Length; - } - - /// - /// SIMD-optimized memory copy for large buffers. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyWithSimd(ReadOnlySpan source, Span destination) - { - var vectorSize = Vector.Count; - var i = 0; - var length = source.Length; - - // Process full vectors - var vectorCount = length / vectorSize; - for (var v = 0; v < vectorCount; v++) - { - var vec = new Vector(source.Slice(i, vectorSize)); - vec.CopyTo(destination.Slice(i, vectorSize)); - i += vectorSize; - } - - // Copy remaining bytes - if (i < length) - { - source.Slice(i).CopyTo(destination.Slice(i)); - } - } - - /// - /// Write double array using SIMD bulk copy (no per-element type codes). - /// For use when caller handles type codes separately. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteDoubleBulkRaw(ReadOnlySpan values) - { - var byteSpan = MemoryMarshal.AsBytes(values); - WriteBytesSimd(byteSpan); - } - - /// - /// Write float array using SIMD bulk copy. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFloatBulkRaw(ReadOnlySpan values) - { - var byteSpan = MemoryMarshal.AsBytes(values); - WriteBytesSimd(byteSpan); - } - - /// - /// Write Guid array using SIMD bulk copy. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteGuidBulkRaw(ReadOnlySpan values) - { - // Guid is 16 bytes, perfect for SIMD - var byteLength = values.Length * 16; - EnsureCapacity(byteLength); - - for (var i = 0; i < values.Length; i++) - { - values[i].TryWriteBytes(_buffer.AsSpan(_position, 16)); - _position += 16; - } - } - - #endregion - #region Header - // Marker-based interning: no footer needed - // Header: [version][flags][cacheCount (VarUInt, if caching enabled)] - // Body: data with markers (StringInternFirst, ObjectRefFirst, etc.) - /// /// Writes the binary header directly. Call AFTER ScanForDuplicates (cacheCount is known). /// No placeholder, no shift — single forward write. @@ -892,48 +370,17 @@ public static partial class AcBinarySerializer if (HasCaching) flags |= BinaryTypeCode.HeaderFlag_HasCacheCount; - WriteByte(AcBinarySerializerOptions.FormatVersion); - WriteByte(flags); + Output.WriteByte(AcBinarySerializerOptions.FormatVersion); + Output.WriteByte(flags); if (HasCaching) { - WriteVarUInt((uint)GetCacheCount()); + Output.WriteVarUInt((uint)GetCacheCount()); } } #endregion - #region Reference Handling - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj); - - /// - /// IId-aware tracking for the scan phase. - /// First checks IId match (different instance, same Id), then falls back to ReferenceEquals. - /// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public bool TrackForScanningWithIId(object obj, BinarySerializeTypeMetadata metadata, out int existingRefId) - //{ - // if (!ReferenceHandling) - // { - // existingRefId = 0; - // return true; // No tracking needed - // } - // return _refTracker.TrackForScanningWithIId(obj, metadata, out existingRefId); - //} - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public bool ShouldWriteRef(object obj, out int refId) => _refTracker.ShouldWriteId(obj, out refId); - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public void MarkAsWritten(object obj, int refId) => _refTracker.MarkAsWritten(obj, refId); - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId); - - #endregion - #region Helpers [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -968,79 +415,5 @@ public static partial class AcBinarySerializer } #endregion - - #region FixStr Methods - - /// - /// Write short ASCII string using FixStr encoding (type+length in single byte). - /// Only call when string is ASCII and length <= 31. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFixStr(string value) - { - var length = value.Length; - EnsureCapacity(1 + length); - _buffer[_position++] = BinaryTypeCode.EncodeFixStr(length); - Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, length), out _); - _position += length; - } - - /// - /// Optimized FixStr write: tries SIMD ASCII conversion, falls back to UTF8. - /// Single-pass: uses Ascii.FromUtf16 which does validation + copy. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFixStrDirect(string value) - { - var length = value.Length; - EnsureCapacity(1 + length); - - // Ascii.FromUtf16: SIMD-optimized ASCII conversion - // Returns actual bytes written - if less than input length, there was a non-ASCII char - var destSpan = _buffer.AsSpan(_position + 1, length); - var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten); - - if (status == System.Buffers.OperationStatus.Done && bytesWritten == length) - { - // Success - write FixStr header - _buffer[_position] = BinaryTypeCode.EncodeFixStr(length); - _position += 1 + length; - } - else - { - // Non-ASCII or partial - use standard string encoding - _buffer[_position++] = BinaryTypeCode.String; - WriteStringUtf8Internal(value); - } - } - - /// - /// Internal string write (after String type code already written). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringUtf8Internal(string value) - { - var byteCount = Utf8NoBom.GetByteCount(value); - WriteVarUInt((uint)byteCount); - EnsureCapacity(byteCount); - Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount)); - _position += byteCount; - } - - /// - /// Write short UTF8 bytes using FixStr encoding. - /// Only call when byteLength <= 31. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteFixStrBytes(ReadOnlySpan utf8Bytes) - { - var length = utf8Bytes.Length; - EnsureCapacity(1 + length); - _buffer[_position++] = BinaryTypeCode.EncodeFixStr(length); - utf8Bytes.CopyTo(_buffer.AsSpan(_position, length)); - _position += length; - } - - #endregion } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializeTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializeTypeMetadata.cs index 7cf0410..ab31d3b 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializeTypeMetadata.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializeTypeMetadata.cs @@ -144,7 +144,7 @@ public static partial class AcBinarySerializer NeedsReferenceTracking = IsIId || HasComplexProperties || !IsPrimitiveType; // Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute - if (type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false)) + if (false && type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false)) { GeneratedSerializerType = FindGeneratedSerializerType(type); HasGeneratedSerializer = GeneratedSerializerType != null; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs index ab36030..c2ff1ab 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.ScanPass.cs @@ -15,7 +15,8 @@ public static partial class AcBinarySerializer /// - Strings/objects skipped here are never written anyway (parent is ObjectRef) /// CacheIndex is assigned immediately on 2nd occurrence (no post-processing needed). /// - private static void ScanForDuplicates(object value, Type type, BinarySerializationContext context) + private static void ScanForDuplicates(object value, Type type, BinarySerializationContext context) + where TOutput : BinaryOutputBase { if (!context.HasCaching) return; @@ -24,7 +25,8 @@ public static partial class AcBinarySerializer ScanValue(value, wrapper, context, 0); } - private static void ScanValue(object? value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void ScanValue(object? value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { if (value == null || depth > context.MaxDepth) return; @@ -128,7 +130,8 @@ public static partial class AcBinarySerializer /// Scans a collection item. Handles string fast path and gets wrapper for the runtime type. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ScanItem(object item, BinarySerializationContext context, int depth) + private static void ScanItem(object item, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { // String fast path — avoid GetWrapper entirely if (item is string str) diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index aa4964e..8b5c485 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -25,6 +25,7 @@ namespace AyCode.Core.Serializers.Binaries; /// - Optimized buffer management with ArrayPool /// - Zero-allocation hot paths using Span and MemoryMarshal /// - Automatic Expression to AcExpressionNode conversion +/// - Generic TOutput for JIT devirtualization (ArrayBinaryOutput / BufferWriterBinaryOutput) /// public static partial class AcBinarySerializer { @@ -57,9 +58,9 @@ public static partial class AcBinarySerializer public static Dictionary> AnalyzeStringInternCandidates(T value, AcBinarySerializerOptions? options = null) { ArgumentNullException.ThrowIfNull(value); - + options ??= AcBinarySerializerOptions.Default; - + // For analysis, use the provided reference handling mode var analysisOptions = new AcBinarySerializerOptions { @@ -68,13 +69,14 @@ public static partial class AcBinarySerializer MaxStringInternLength = options.MaxStringInternLength, ReferenceHandling = options.ReferenceHandling }; - + var result = new Dictionary>(); var runtimeType = value.GetType(); - + // Create context without pooling (we need to set up callback) - using var context = new BinarySerializationContext(analysisOptions); - + using var context = new BinarySerializationContext(analysisOptions); + context.Output = new ArrayBinaryOutput(); + // Set up tracking callbacks context.OnStringInterned = (propertyPath, stringValue) => { @@ -88,11 +90,11 @@ public static partial class AcBinarySerializer properties[propertyPath] = properties.GetValueOrDefault(propertyPath) + 1; }; - + // Run serialization to trigger callbacks context.WriteHeader(); WriteValue(value, runtimeType, context, 0); - + return result; } @@ -104,7 +106,7 @@ public static partial class AcBinarySerializer // Transform: stringValue → properties TO propertyPath → (stringValue, count) var propertyStats = new Dictionary>(); - + foreach (var (stringValue, properties) in analysis) { var byteLength = Encoding.UTF8.GetByteCount(stringValue); @@ -131,7 +133,7 @@ public static partial class AcBinarySerializer var totalStrings = analysis.Values.Sum(p => p.Values.Sum()); var uniqueStrings = analysis.Count; var repeatedStrings = analysis.Count(kv => kv.Value.Values.Sum() > 1); - + sb.AppendLine("+-----------------------------------------------------------------------------+"); sb.AppendLine("| STRING SUMMARY |"); sb.AppendLine("+-----------------------------------------------------------------------------+"); @@ -150,13 +152,13 @@ public static partial class AcBinarySerializer var repeated = strings.Count(s => s.Count > 1); var repeatSum = strings.Where(s => s.Count > 1).Sum(s => s.Count); // Sum of occurrences of repeated strings var repeatSumPct = total > 0 ? repeatSum * 100.0 / total : 0; - + // Calculate savings var totalBytes = strings.Sum(s => s.Count * s.ByteLength); var uniqueBytes = strings.Sum(s => s.ByteLength); var indexBytes = total * 2; var savings = totalBytes - (uniqueBytes + indexBytes); - + return (propPath, strings, total, unique, repeated, repeatSum, repeatSumPct, savings); }).ToList(); @@ -168,7 +170,7 @@ public static partial class AcBinarySerializer { var savingsStr = stat.savings > 0 ? $"+{stat.savings:N0}B" : $"{stat.savings:N0}B"; var recommend = stat.savings > 100 ? "[INTERN]" : stat.savings > 0 ? " maybe " : " skip "; - + var propDisplay = stat.propPath.Length > 30 ? stat.propPath[..27] + "..." : stat.propPath; sb.AppendLine($"| {propDisplay,-30} | {stat.total,5} | {stat.repeatSum,7} | {stat.unique,6} | {stat.repeated,7} | {stat.repeatSumPct,8:F1}% | {savingsStr,8} | {recommend,-11} |"); } @@ -187,14 +189,14 @@ public static partial class AcBinarySerializer { sb.AppendLine(); sb.AppendLine($" {stat.propPath} (RepSum: {stat.repeatSum}/{stat.total} = {stat.repeatSumPct:F1}%):"); - + foreach (var (strVal, count, _) in stat.strings.OrderByDescending(s => s.Count).Take(10)) { var preview = strVal.Length > 40 ? strVal[..37] + "..." : strVal; var marker = count > 1 ? ">" : " "; sb.AppendLine($" {marker} [{count,4}x] \"{preview}\""); } - + if (stat.strings.Count > 10) { sb.AppendLine($" ... and {stat.strings.Count - 10} more unique values"); @@ -208,7 +210,7 @@ public static partial class AcBinarySerializer sb.AppendLine(" RepSum%=percentage of occurrences that are repeated (higher=better for intern)"); sb.AppendLine(" Savings=estimated bytes saved with interning (positive=good)"); sb.AppendLine(" > = repeated string (benefits from interning)"); - + return sb; } #endif @@ -227,6 +229,7 @@ public static partial class AcBinarySerializer /// /// Serialize object to binary with specified options. + /// Uses ArrayBinaryOutput for byte[] result path. /// public static byte[] Serialize(T value, AcBinarySerializerOptions options) { @@ -236,7 +239,7 @@ public static partial class AcBinarySerializer } var runtimeType = value.GetType(); - + // Handle IQueryable types - convert to AcExpressionNode (serialize the Expression) object actualValue = value; if (value is IQueryable queryable) @@ -250,29 +253,38 @@ public static partial class AcBinarySerializer actualValue = AcExpressionConverter.ToNode((Expression)(object)value); runtimeType = typeof(AcExpressionNode); } - - var context = SerializeCore(actualValue, runtimeType, options); + + var context = BinarySerializationContextPool.Get(options); + if (context.Output == null) + context.Output = new ArrayBinaryOutput(options.InitialBufferCapacity); + else + context.Output.Reset(); + try { + ScanForDuplicates(actualValue, runtimeType, context); + context.WriteHeader(); + WriteValue(actualValue, runtimeType, context, 0); + // Apply compression if enabled - compress directly from buffer span (1 allocation) if (options.UseCompression != Lz4CompressionMode.None) { - return Lz4.Compress(context.AsSpan(), options.UseCompression); + return Lz4.Compress(context.Output.AsSpan(), options.UseCompression); } - + // No compression - single allocation for result - return context.ToArray(); + return context.Output.ToArray(); } finally { - if (context.Options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); - else BinarySerializationContextPool.Return(context); + if (options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); + else BinarySerializationContextPool.Return(context); } } /// /// Serialize object to an IBufferWriter for zero-copy scenarios. - /// This avoids the final ToArray() allocation by writing directly to the caller's buffer. + /// Uses BufferWriterBinaryOutput — writes directly to the caller's buffer. /// Note: Compression is applied if enabled in options. /// public static void Serialize(T value, IBufferWriter writer, AcBinarySerializerOptions options) @@ -286,7 +298,7 @@ public static partial class AcBinarySerializer } var runtimeType = value.GetType(); - + // Handle IQueryable types - convert to AcExpressionNode (serialize the Expression) object actualValue = value; if (value is IQueryable queryable) @@ -300,27 +312,37 @@ public static partial class AcBinarySerializer actualValue = AcExpressionConverter.ToNode((Expression)(object)value); runtimeType = typeof(AcExpressionNode); } - - var context = SerializeCore(actualValue, runtimeType, options); + + var output = new BufferWriterBinaryOutput(writer); + var context = BinarySerializationContextPool.Get(options); + context.Output = output; + try { - // Apply compression if enabled - compress directly from buffer span (1 allocation) + ScanForDuplicates(actualValue, runtimeType, context); + context.WriteHeader(); + WriteValue(actualValue, runtimeType, context, 0); + + // Apply compression if enabled if (options.UseCompression != Lz4CompressionMode.None) { - var compressed = Lz4.Compress(context.AsSpan(), options.UseCompression); - var destSpan = writer.GetSpan(compressed.Length); - compressed.CopyTo(destSpan); - writer.Advance(compressed.Length); - } - else - { - context.WriteTo(writer); + // For compression with BufferWriter, we need to flush first then compress + // This path is less common — compression typically uses byte[] path + output.Flush(); + // Compression with IBufferWriter requires intermediate buffer + // Fall back to ArrayBinaryOutput path for compression + throw new NotSupportedException( + "Compression is not supported with IBufferWriter output. " + + "Use the byte[] overload or disable compression."); } + + output.Flush(); } finally { - if (context.Options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); - else BinarySerializationContextPool.Return(context); + context.Output = null!; + if (options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); + else BinarySerializationContextPool.Return(context); } } @@ -333,15 +355,24 @@ public static partial class AcBinarySerializer if (value == null) return 1; var runtimeType = value.GetType(); - var context = SerializeCore(value, runtimeType, options); + + var context = BinarySerializationContextPool.Get(options); + if (context.Output == null) + context.Output = new ArrayBinaryOutput(options.InitialBufferCapacity); + else + context.Output.Reset(); + try { + ScanForDuplicates(value, runtimeType, context); + context.WriteHeader(); + WriteValue(value, runtimeType, context, 0); return context.Position; } finally { - if (context.Options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); - else BinarySerializationContextPool.Return(context); + if (options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); + else BinarySerializationContextPool.Return(context); } } @@ -358,61 +389,54 @@ public static partial class AcBinarySerializer } var runtimeType = value.GetType(); - var context = SerializeCore(value, runtimeType, options); + + var context = BinarySerializationContextPool.Get(options); + if (context.Output == null) + context.Output = new ArrayBinaryOutput(options.InitialBufferCapacity); + else + context.Output.Reset(); + try { + ScanForDuplicates(value, runtimeType, context); + context.WriteHeader(); + WriteValue(value, runtimeType, context, 0); + // If compression enabled, compress directly from buffer span (1 allocation) if (options.UseCompression != Lz4CompressionMode.None) { - var compressed = Lz4.Compress(context.AsSpan(), options.UseCompression); + var compressed = Lz4.Compress(context.Output.AsSpan(), options.UseCompression); return BinarySerializationResult.FromImmutable(compressed); } - - return context.DetachResult(); + + return context.Output.DetachResult(); } finally { - if (context.Options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); - else BinarySerializationContextPool.Return(context); + if (options.UseAsync) BinarySerializationContextPool.ReturnAsync(context); + else BinarySerializationContextPool.Return(context); } } - private static BinarySerializationContext SerializeCore(object value, Type runtimeType, AcBinarySerializerOptions options) - { -#if DEBUG - BinarySerializationContext.GrowBufferCount = 0; - BinarySerializationContext.GrowBufferTotalBytes = 0; -#endif - var context = BinarySerializationContextPool.Get(options); - - // Two-pass serialization: - // 1. Scan pass: identify duplicates (strings + objects), assign CacheIndex - // 2. Write header (cacheCount is now known - no placeholder, no shift) - // 3. Serialize pass: write body with references - ScanForDuplicates(value, runtimeType, context); - context.WriteHeader(); - WriteValue(value, runtimeType, context, 0); - - return context; - } - #endregion #region Value Writing - private static void WriteValue(object? value, Type type, BinarySerializationContext context, int depth) + private static void WriteValue(object? value, Type type, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { + var output = context.Output; if (value == null) { - context.WriteByte(BinaryTypeCode.Null); + output.WriteByte(BinaryTypeCode.Null); return; } // Try writing as primitive first - if (TryWritePrimitive(value, type, context)) + if (TryWritePrimitive(value, type, output, context)) return; - WriteValueNonPrimitive(value, type, context, depth); + WriteValueNonPrimitive(value, type, output, context, depth); } /// @@ -420,26 +444,27 @@ public static partial class AcBinarySerializer /// Skips null check and TryWritePrimitive — caller guarantees value is non-null and not a primitive type. /// Called from WritePropertyOrSkip default case (PropertyAccessorType.Object) and WriteValue fallback. /// - private static void WriteValueNonPrimitive(object value, Type type, BinarySerializationContext context, int depth) + private static void WriteValueNonPrimitive(object value, Type type, TOutput output, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { // Nullable where T is a value type: boxed value may be a primitive. // Only Nullable can be a value type in the Object accessor path. if (type.IsValueType) { - if (TryWritePrimitive(value, value.GetType(), context)) + if (TryWritePrimitive(value, value.GetType(), output, context)) return; } if (depth > context.MaxDepth) { - context.WriteByte(BinaryTypeCode.Null); + output.WriteByte(BinaryTypeCode.Null); return; } // Handle byte arrays specially (value-like, no reference tracking) if (value is byte[] byteArray) { - WriteByteArray(byteArray, context); + WriteByteArray(byteArray, output); return; } @@ -469,81 +494,82 @@ public static partial class AcBinarySerializer /// Avoids Nullable.GetUnderlyingType in hot path by using cached type info. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryWritePrimitive(object value, Type type, BinarySerializationContext context) + private static bool TryWritePrimitive(object value, Type type, TOutput output, BinarySerializationContext context) + where TOutput : BinaryOutputBase { // Fast path: check TypeCode first (handles most primitives) var typeCode = Type.GetTypeCode(type); - + switch (typeCode) { case TypeCode.Int32: - WriteInt32((int)value, context); + WriteInt32((int)value, output); return true; case TypeCode.Int64: - WriteInt64((long)value, context); + WriteInt64((long)value, output); return true; case TypeCode.Boolean: - context.WriteByte((bool)value ? BinaryTypeCode.True : BinaryTypeCode.False); + output.WriteByte((bool)value ? BinaryTypeCode.True : BinaryTypeCode.False); return true; case TypeCode.Double: - WriteFloat64Unsafe((double)value, context); + WriteFloat64Unsafe((double)value, output); return true; case TypeCode.String: - WriteString((string)value, context); + WriteString((string)value, output, context); return true; case TypeCode.Single: - WriteFloat32Unsafe((float)value, context); + WriteFloat32Unsafe((float)value, output); return true; case TypeCode.Decimal: - WriteDecimalUnsafe((decimal)value, context); + WriteDecimalUnsafe((decimal)value, output); return true; case TypeCode.DateTime: - WriteDateTimeUnsafe((DateTime)value, context); + WriteDateTimeUnsafe((DateTime)value, output); return true; case TypeCode.Byte: - context.WriteByte(BinaryTypeCode.UInt8); - context.WriteByte((byte)value); + output.WriteByte(BinaryTypeCode.UInt8); + output.WriteByte((byte)value); return true; case TypeCode.Int16: - WriteInt16Unsafe((short)value, context); + WriteInt16Unsafe((short)value, output); return true; case TypeCode.UInt16: - WriteUInt16Unsafe((ushort)value, context); + WriteUInt16Unsafe((ushort)value, output); return true; case TypeCode.UInt32: - WriteUInt32((uint)value, context); + WriteUInt32((uint)value, output); return true; case TypeCode.UInt64: - WriteUInt64((ulong)value, context); + WriteUInt64((ulong)value, output); return true; case TypeCode.SByte: - context.WriteByte(BinaryTypeCode.Int8); - context.WriteByte(unchecked((byte)(sbyte)value)); + output.WriteByte(BinaryTypeCode.Int8); + output.WriteByte(unchecked((byte)(sbyte)value)); return true; case TypeCode.Char: - WriteCharUnsafe((char)value, context); + WriteCharUnsafe((char)value, output); return true; } // Handle special types by reference comparison (faster than type equality) if (ReferenceEquals(type, GuidType)) { - WriteGuidUnsafe((Guid)value, context); + WriteGuidUnsafe((Guid)value, output); return true; } if (ReferenceEquals(type, DateTimeOffsetType)) { - WriteDateTimeOffsetUnsafe((DateTimeOffset)value, context); + WriteDateTimeOffsetUnsafe((DateTimeOffset)value, output); return true; } if (ReferenceEquals(type, TimeSpanType)) { - WriteTimeSpanUnsafe((TimeSpan)value, context); + WriteTimeSpanUnsafe((TimeSpan)value, output); return true; } if (type.IsEnum) { - WriteEnum(value, context); + WriteEnum(value, output); return true; } @@ -553,7 +579,7 @@ public static partial class AcBinarySerializer { // When boxed, nullable value types are unwrapped to their underlying type // So we can just call TryWritePrimitive with the actual runtime type - return TryWritePrimitive(value, value.GetType(), context); + return TryWritePrimitive(value, value.GetType(), output, context); } return false; @@ -564,143 +590,158 @@ public static partial class AcBinarySerializer #region Optimized Primitive Writers using MemoryMarshal [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteInt32(int value, BinarySerializationContext context) + private static void WriteInt32(int value, TOutput output) + where TOutput : BinaryOutputBase { if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny)) { - context.WriteByte(tiny); + output.WriteByte(tiny); return; } - context.WriteByte(BinaryTypeCode.Int32); - context.WriteVarInt(value); + output.WriteByte(BinaryTypeCode.Int32); + output.WriteVarInt(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteInt64(long value, BinarySerializationContext context) + private static void WriteInt64(long value, TOutput output) + where TOutput : BinaryOutputBase { if (value >= int.MinValue && value <= int.MaxValue) { - WriteInt32((int)value, context); + WriteInt32((int)value, output); return; } - context.WriteByte(BinaryTypeCode.Int64); - context.WriteVarLong(value); + output.WriteByte(BinaryTypeCode.Int64); + output.WriteVarLong(value); } /// /// Optimized float64 writer using batched write. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteFloat64Unsafe(double value, BinarySerializationContext context) + private static void WriteFloat64Unsafe(double value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, value); + output.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, value); } /// /// Optimized float32 writer using batched write. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteFloat32Unsafe(float value, BinarySerializationContext context) + private static void WriteFloat32Unsafe(float value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, value); + output.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, value); } /// /// Optimized decimal writer using direct memory copy of bits. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDecimalUnsafe(decimal value, BinarySerializationContext context) + private static void WriteDecimalUnsafe(decimal value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.Decimal); - context.WriteDecimalBits(value); + output.WriteByte(BinaryTypeCode.Decimal); + output.WriteDecimalBits(value); } /// /// Optimized DateTime writer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDateTimeUnsafe(DateTime value, BinarySerializationContext context) + private static void WriteDateTimeUnsafe(DateTime value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.DateTime); - context.WriteDateTimeBits(value); + output.WriteByte(BinaryTypeCode.DateTime); + output.WriteDateTimeBits(value); } /// /// Optimized Guid writer using direct memory copy. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteGuidUnsafe(Guid value, BinarySerializationContext context) + private static void WriteGuidUnsafe(Guid value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.Guid); - context.WriteGuidBits(value); + output.WriteByte(BinaryTypeCode.Guid); + output.WriteGuidBits(value); } /// /// Optimized DateTimeOffset writer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteDateTimeOffsetUnsafe(DateTimeOffset value, BinarySerializationContext context) + private static void WriteDateTimeOffsetUnsafe(DateTimeOffset value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.DateTimeOffset); - context.WriteDateTimeOffsetBits(value); + output.WriteByte(BinaryTypeCode.DateTimeOffset); + output.WriteDateTimeOffsetBits(value); } /// /// Optimized TimeSpan writer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteTimeSpanUnsafe(TimeSpan value, BinarySerializationContext context) + private static void WriteTimeSpanUnsafe(TimeSpan value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, value.Ticks); + output.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, value.Ticks); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteInt16Unsafe(short value, BinarySerializationContext context) + private static void WriteInt16Unsafe(short value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, value); + output.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteUInt16Unsafe(ushort value, BinarySerializationContext context) + private static void WriteUInt16Unsafe(ushort value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, value); + output.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteUInt32(uint value, BinarySerializationContext context) + private static void WriteUInt32(uint value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.UInt32); - context.WriteVarUInt(value); + output.WriteByte(BinaryTypeCode.UInt32); + output.WriteVarUInt(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteUInt64(ulong value, BinarySerializationContext context) + private static void WriteUInt64(ulong value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.UInt64); - context.WriteVarULong(value); + output.WriteByte(BinaryTypeCode.UInt64); + output.WriteVarULong(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteCharUnsafe(char value, BinarySerializationContext context) + private static void WriteCharUnsafe(char value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.Char); - context.WriteRaw(value); + output.WriteByte(BinaryTypeCode.Char); + output.WriteRaw(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteEnum(object value, BinarySerializationContext context) + private static void WriteEnum(object value, TOutput output) + where TOutput : BinaryOutputBase { // Use direct unboxing instead of Convert.ToInt32 to avoid NumberFormatInfo overhead var intValue = GetEnumAsInt32Fast(value); if (BinaryTypeCode.TryEncodeTinyInt(intValue, out var tiny)) { - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(tiny); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(tiny); return; } - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(BinaryTypeCode.Int32); - context.WriteVarInt(intValue); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(BinaryTypeCode.Int32); + output.WriteVarInt(intValue); } /// @@ -712,7 +753,7 @@ public static partial class AcBinarySerializer { var type = enumValue.GetType(); var underlyingType = type.GetEnumUnderlyingType(); - + if (ReferenceEquals(underlyingType, IntType)) return (int)enumValue; if (ReferenceEquals(underlyingType, typeof(byte))) @@ -725,7 +766,7 @@ public static partial class AcBinarySerializer return (ushort)enumValue; if (ReferenceEquals(underlyingType, typeof(uint))) return unchecked((int)(uint)enumValue); - + // Fallback for rare cases (long, ulong) return Convert.ToInt32(enumValue); } @@ -735,11 +776,12 @@ public static partial class AcBinarySerializer /// Marker-based interning: write String marker, rewrite to StringInternFirst at end if needed. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteString(string value, BinarySerializationContext context) + private static void WriteString(string value, TOutput output, BinarySerializationContext context) + where TOutput : BinaryOutputBase { if (value.Length == 0) { - context.WriteByte(BinaryTypeCode.StringEmpty); + output.WriteByte(BinaryTypeCode.StringEmpty); return; } @@ -760,15 +802,15 @@ public static partial class AcBinarySerializer { // 1st serialize occurrence of a cached string - write StringInternFirst + cacheIndex + data interEntry.IsFirstWrite = false; - context.WriteByte(BinaryTypeCode.StringInternFirst); - context.WriteVarUInt((uint)interEntry.CacheIndex); - context.WriteStringUtf8(value); + output.WriteByte(BinaryTypeCode.StringInternFirst); + output.WriteVarUInt((uint)interEntry.CacheIndex); + output.WriteStringUtf8(value); } else { // 2+ serialize occurrence: write index reference - context.WriteByte(BinaryTypeCode.StringInterned); - context.WriteVarUInt((uint)interEntry.CacheIndex); + output.WriteByte(BinaryTypeCode.StringInterned); + output.WriteVarUInt((uint)interEntry.CacheIndex); } return; } @@ -778,8 +820,8 @@ public static partial class AcBinarySerializer context.OnStringInterned?.Invoke(context.CurrentPropertyPath, value); #endif // String not cached (single occurrence or not found) - write plain String - context.WriteByte(BinaryTypeCode.String); - context.WriteStringUtf8(value); + output.WriteByte(BinaryTypeCode.String); + output.WriteStringUtf8(value); return; } @@ -789,29 +831,32 @@ public static partial class AcBinarySerializer if (length <= BinaryTypeCode.FixStrMaxLength) { // For short strings, use direct ASCII copy (avoids double validation) - context.WriteFixStrDirect(value); + output.WriteFixStrDirect(value); return; } // Long strings - standard encoding - context.WriteByte(BinaryTypeCode.String); - context.WriteStringUtf8(value); + output.WriteByte(BinaryTypeCode.String); + output.WriteStringUtf8(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteByteArray(byte[] value, BinarySerializationContext context) + private static void WriteByteArray(byte[] value, TOutput output) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.ByteArray); - context.WriteVarUInt((uint)value.Length); - context.WriteBytes(value); + output.WriteByte(BinaryTypeCode.ByteArray); + output.WriteVarUInt((uint)value.Length); + output.WriteBytes(value); } #endregion #region Complex Type Writers - private static void WriteObject(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth, bool isNested = false) + private static void WriteObject(object value, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth, bool isNested = false) + where TOutput : BinaryOutputBase { + var output = context.Output; var metadata = wrapper.Metadata; // Wire format: @@ -824,7 +869,7 @@ public static partial class AcBinarySerializer var isFirstMetadataOccurrence = false; if (context.UseMetadata) { - isFirstMetadataOccurrence = BinarySerializationContext.RegisterMetadataType(wrapper); + isFirstMetadataOccurrence = BinarySerializationContext.RegisterMetadataType(wrapper); } // Reference handling: lookup entry from scan pass, check IsFirstWrite @@ -852,8 +897,8 @@ public static partial class AcBinarySerializer else { // 2+ occurrence → write ObjectRef - context.WriteByte(BinaryTypeCode.ObjectRef); - context.WriteVarUInt((uint)entry.CacheIndex); + output.WriteByte(BinaryTypeCode.ObjectRef); + output.WriteVarUInt((uint)entry.CacheIndex); return; } } @@ -876,8 +921,8 @@ public static partial class AcBinarySerializer } else { - context.WriteByte(BinaryTypeCode.ObjectRef); - context.WriteVarUInt((uint)entry.CacheIndex); + output.WriteByte(BinaryTypeCode.ObjectRef); + output.WriteVarUInt((uint)entry.CacheIndex); return; } } @@ -900,8 +945,8 @@ public static partial class AcBinarySerializer } else { - context.WriteByte(BinaryTypeCode.ObjectRef); - context.WriteVarUInt((uint)entry.CacheIndex); + output.WriteByte(BinaryTypeCode.ObjectRef); + output.WriteVarUInt((uint)entry.CacheIndex); return; } } @@ -911,32 +956,32 @@ public static partial class AcBinarySerializer } } - // Marker kiírása: + // Marker kiírása: // - Cached object first occurrence: ObjectRefFirst/ObjectWithMetadataRefFirst + cacheIndex // - Non-cached: Object/ObjectWithMetadata if (context.UseMetadata) { if (cachedObjectCacheIndex >= 0) { - context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst); - context.WriteVarUInt((uint)cachedObjectCacheIndex); + output.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst); + output.WriteVarUInt((uint)cachedObjectCacheIndex); } else { - context.WriteByte(BinaryTypeCode.ObjectWithMetadata); + output.WriteByte(BinaryTypeCode.ObjectWithMetadata); } - context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence); + context.WriteInlineMetadata(wrapper.Metadata, output, isFirstMetadataOccurrence); } else { if (cachedObjectCacheIndex >= 0) { - context.WriteByte(BinaryTypeCode.ObjectRefFirst); - context.WriteVarUInt((uint)cachedObjectCacheIndex); + output.WriteByte(BinaryTypeCode.ObjectRefFirst); + output.WriteVarUInt((uint)cachedObjectCacheIndex); } else { - context.WriteByte(BinaryTypeCode.Object); + output.WriteByte(BinaryTypeCode.Object); } } @@ -957,15 +1002,15 @@ public static partial class AcBinarySerializer if (prop.ExpectedTypeCode.HasValue) { - WritePropertyMarkerless(value, prop, context); + WritePropertyMarkerless(value, prop, output); } else if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop)) { - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); } else { - WritePropertyOrSkip(value, prop, context, nextDepth); + WritePropertyOrSkip(value, prop, output, context, nextDepth); } } } @@ -978,11 +1023,11 @@ public static partial class AcBinarySerializer if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop)) { - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); continue; } - WritePropertyOrSkip(value, prop, context, nextDepth); + WritePropertyOrSkip(value, prop, output, context, nextDepth); } } } @@ -1037,62 +1082,63 @@ public static partial class AcBinarySerializer /// Writes a property value using typed getters to avoid boxing. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WritePropertyValue(object obj, BinaryPropertyAccessor prop, BinarySerializationContext context, int depth) + private static void WritePropertyValue(object obj, BinaryPropertyAccessor prop, TOutput output, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { switch (prop.AccessorType) { case PropertyAccessorType.Int32: - WriteInt32(prop.GetInt32(obj), context); + WriteInt32(prop.GetInt32(obj), output); return; case PropertyAccessorType.Int64: - WriteInt64(prop.GetInt64(obj), context); + WriteInt64(prop.GetInt64(obj), output); return; case PropertyAccessorType.Boolean: - context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False); + output.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False); return; case PropertyAccessorType.Double: - WriteFloat64Unsafe(prop.GetDouble(obj), context); + WriteFloat64Unsafe(prop.GetDouble(obj), output); return; case PropertyAccessorType.Single: - WriteFloat32Unsafe(prop.GetSingle(obj), context); + WriteFloat32Unsafe(prop.GetSingle(obj), output); return; case PropertyAccessorType.Decimal: - WriteDecimalUnsafe(prop.GetDecimal(obj), context); + WriteDecimalUnsafe(prop.GetDecimal(obj), output); return; case PropertyAccessorType.DateTime: - WriteDateTimeUnsafe(prop.GetDateTime(obj), context); + WriteDateTimeUnsafe(prop.GetDateTime(obj), output); return; case PropertyAccessorType.Byte: - context.WriteByte(BinaryTypeCode.UInt8); - context.WriteByte(prop.GetByte(obj)); + output.WriteByte(BinaryTypeCode.UInt8); + output.WriteByte(prop.GetByte(obj)); return; case PropertyAccessorType.Int16: - WriteInt16Unsafe(prop.GetInt16(obj), context); + WriteInt16Unsafe(prop.GetInt16(obj), output); return; case PropertyAccessorType.UInt16: - WriteUInt16Unsafe(prop.GetUInt16(obj), context); + WriteUInt16Unsafe(prop.GetUInt16(obj), output); return; case PropertyAccessorType.UInt32: - WriteUInt32(prop.GetUInt32(obj), context); + WriteUInt32(prop.GetUInt32(obj), output); return; case PropertyAccessorType.UInt64: - WriteUInt64(prop.GetUInt64(obj), context); + WriteUInt64(prop.GetUInt64(obj), output); return; case PropertyAccessorType.Guid: - WriteGuidUnsafe(prop.GetGuid(obj), context); + WriteGuidUnsafe(prop.GetGuid(obj), output); return; case PropertyAccessorType.Enum: var enumValue = prop.GetEnumAsInt32(obj); if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny)) { - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(tiny); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(tiny); } else { - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(BinaryTypeCode.Int32); - context.WriteVarInt(enumValue); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(BinaryTypeCode.Int32); + output.WriteVarInt(enumValue); } return; case PropertyAccessorType.String: @@ -1100,9 +1146,9 @@ public static partial class AcBinarySerializer // Fast path: typed getter, no boxing, no Type.GetTypeCode() call var strValue = prop.GetString(obj); if (strValue != null) - WriteString(strValue, context); + WriteString(strValue, output, context); else - context.WriteByte(BinaryTypeCode.Null); + output.WriteByte(BinaryTypeCode.Null); return; } default: @@ -1119,80 +1165,82 @@ public static partial class AcBinarySerializer /// Avoids double getter calls. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WritePropertyOrSkip(object obj, BinaryPropertyAccessor prop, BinarySerializationContext context, int depth) + private static void WritePropertyOrSkip(object obj, BinaryPropertyAccessor prop, TOutput output, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { + //var output = context.Output; switch (prop.AccessorType) { case PropertyAccessorType.Int32: { int value = prop.GetInt32(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteInt32(value, context); + WriteInt32(value, output); return; } case PropertyAccessorType.Int64: { long value = prop.GetInt64(obj); if (value == 0L) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteInt64(value, context); + WriteInt64(value, output); return; } case PropertyAccessorType.Boolean: { bool value = prop.GetBoolean(obj); if (!value) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - context.WriteByte(BinaryTypeCode.True); + output.WriteByte(BinaryTypeCode.True); return; } case PropertyAccessorType.Double: { double value = prop.GetDouble(obj); if (value == 0.0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteFloat64Unsafe(value, context); + WriteFloat64Unsafe(value, output); return; } case PropertyAccessorType.Single: { float value = prop.GetSingle(obj); if (value == 0f) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteFloat32Unsafe(value, context); + WriteFloat32Unsafe(value, output); return; } case PropertyAccessorType.Decimal: { decimal value = prop.GetDecimal(obj); if (value == 0m) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteDecimalUnsafe(value, context); + WriteDecimalUnsafe(value, output); return; } case PropertyAccessorType.DateTime: { DateTime value = prop.GetDateTime(obj); // DateTime always written (no default skip) - WriteDateTimeUnsafe(value, context); + WriteDateTimeUnsafe(value, output); return; } case PropertyAccessorType.Byte: { byte value = prop.GetByte(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else { - context.WriteByte(BinaryTypeCode.UInt8); - context.WriteByte(value); + output.WriteByte(BinaryTypeCode.UInt8); + output.WriteByte(value); } return; } @@ -1200,45 +1248,45 @@ public static partial class AcBinarySerializer { short value = prop.GetInt16(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteInt16Unsafe(value, context); + WriteInt16Unsafe(value, output); return; } case PropertyAccessorType.UInt16: { ushort value = prop.GetUInt16(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteUInt16Unsafe(value, context); + WriteUInt16Unsafe(value, output); return; } case PropertyAccessorType.UInt32: { uint value = prop.GetUInt32(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteUInt32(value, context); + WriteUInt32(value, output); return; } case PropertyAccessorType.UInt64: { ulong value = prop.GetUInt64(obj); if (value == 0) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteUInt64(value, context); + WriteUInt64(value, output); return; } case PropertyAccessorType.Guid: { Guid value = prop.GetGuid(obj); if (value == Guid.Empty) - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); else - WriteGuidUnsafe(value, context); + WriteGuidUnsafe(value, output); return; } case PropertyAccessorType.Enum: @@ -1246,18 +1294,18 @@ public static partial class AcBinarySerializer int enumValue = prop.GetEnumAsInt32(obj); if (enumValue == 0) { - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); } else if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny)) { - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(tiny); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(tiny); } else { - context.WriteByte(BinaryTypeCode.Enum); - context.WriteByte(BinaryTypeCode.Int32); - context.WriteVarInt(enumValue); + output.WriteByte(BinaryTypeCode.Enum); + output.WriteByte(BinaryTypeCode.Int32); + output.WriteVarInt(enumValue); } return; } @@ -1267,14 +1315,14 @@ public static partial class AcBinarySerializer string? value = prop.GetString(obj); if (string.IsNullOrEmpty(value)) { - context.WriteByte(value == null ? BinaryTypeCode.PropertySkip : BinaryTypeCode.StringEmpty); + output.WriteByte(value == null ? BinaryTypeCode.PropertySkip : BinaryTypeCode.StringEmpty); } else { #if DEBUG context.CurrentPropertyPath = $"{prop.DeclaringType.Name}.{prop.Name}"; #endif - WriteString(value, context); + WriteString(value, output, context); } return; } @@ -1288,14 +1336,14 @@ public static partial class AcBinarySerializer // Empty string, empty collections, etc. are valid values and must be written! if (value == null) { - context.WriteByte(BinaryTypeCode.PropertySkip); + output.WriteByte(BinaryTypeCode.PropertySkip); } else { #if DEBUG context.CurrentPropertyPath = $"{prop.DeclaringType.Name}.{prop.Name}"; #endif - WriteValueNonPrimitive(value, prop.PropertyType, context, depth); + WriteValueNonPrimitive(value, prop.PropertyType, output, context, depth); } return; } @@ -1308,45 +1356,46 @@ public static partial class AcBinarySerializer /// Only called for non-nullable value types with ExpectedTypeCode set. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WritePropertyMarkerless(object obj, BinaryPropertyAccessor prop, BinarySerializationContext context) + private static void WritePropertyMarkerless(object obj, BinaryPropertyAccessor prop, TOutput output) + where TOutput : BinaryOutputBase { switch (prop.AccessorType) { case PropertyAccessorType.Int32: - context.WriteVarInt(prop.GetInt32(obj)); + output.WriteVarInt(prop.GetInt32(obj)); return; case PropertyAccessorType.Int64: - context.WriteVarLong(prop.GetInt64(obj)); + output.WriteVarLong(prop.GetInt64(obj)); return; case PropertyAccessorType.Double: - context.WriteRaw(prop.GetDouble(obj)); + output.WriteRaw(prop.GetDouble(obj)); return; case PropertyAccessorType.Single: - context.WriteRaw(prop.GetSingle(obj)); + output.WriteRaw(prop.GetSingle(obj)); return; case PropertyAccessorType.Decimal: - context.WriteDecimalBits(prop.GetDecimal(obj)); + output.WriteDecimalBits(prop.GetDecimal(obj)); return; case PropertyAccessorType.DateTime: - context.WriteDateTimeBits(prop.GetDateTime(obj)); + output.WriteDateTimeBits(prop.GetDateTime(obj)); return; case PropertyAccessorType.Guid: - context.WriteGuidBits(prop.GetGuid(obj)); + output.WriteGuidBits(prop.GetGuid(obj)); return; case PropertyAccessorType.Byte: - context.WriteByte(prop.GetByte(obj)); + output.WriteByte(prop.GetByte(obj)); return; case PropertyAccessorType.Int16: - context.WriteRaw(prop.GetInt16(obj)); + output.WriteRaw(prop.GetInt16(obj)); return; case PropertyAccessorType.UInt16: - context.WriteRaw(prop.GetUInt16(obj)); + output.WriteRaw(prop.GetUInt16(obj)); return; case PropertyAccessorType.UInt32: - context.WriteVarUInt(prop.GetUInt32(obj)); + output.WriteVarUInt(prop.GetUInt32(obj)); return; case PropertyAccessorType.UInt64: - context.WriteVarULong(prop.GetUInt64(obj)); + output.WriteVarULong(prop.GetUInt64(obj)); return; } } @@ -1358,19 +1407,21 @@ public static partial class AcBinarySerializer /// /// Optimized array writer with specialized paths for primitive arrays. /// - private static void WriteArray(IEnumerable enumerable, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + private static void WriteArray(IEnumerable enumerable, TypeMetadataWrapper wrapper, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.Array); + var output = context.Output; + output.WriteByte(BinaryTypeCode.Array); var nextDepth = depth + 1; // Use pre-computed metadata — no GetWrapper or GetCollectionElementType needed var metadata = wrapper.Metadata; var elementType = metadata.CollectionElementType; - + // Optimized path for primitive arrays if (elementType != null && metadata.SourceType.IsArray) { - if (TryWritePrimitiveArray(enumerable, elementType, context)) + if (TryWritePrimitiveArray(enumerable, elementType, output, context)) return; } @@ -1378,7 +1429,7 @@ public static partial class AcBinarySerializer if (enumerable is IList list) { var count = list.Count; - context.WriteVarUInt((uint)count); + output.WriteVarUInt((uint)count); for (var i = 0; i < count; i++) { var item = list[i]; @@ -1395,7 +1446,7 @@ public static partial class AcBinarySerializer items.Add(item); } - context.WriteVarUInt((uint)items.Count); + output.WriteVarUInt((uint)items.Count); foreach (var item in items) { var itemType = item?.GetType() ?? typeof(object); @@ -1405,70 +1456,96 @@ public static partial class AcBinarySerializer /// /// Specialized array writer for primitive arrays using bulk memory operations. - /// Optimized for Blazor Hybrid compatibility (WASM, Android, Windows, iOS). + /// Non-generic to avoid JIT code duplication — virtual dispatch cost is negligible + /// because this is called once per array, not per element. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryWritePrimitiveArray(IEnumerable enumerable, Type elementType, BinarySerializationContext context) + private static bool TryWritePrimitiveArray(IEnumerable enumerable, Type elementType, TOutput output, BinarySerializationContext context) + where TOutput : BinaryOutputBase + { + // String array needs context for interning — keep generic path + if (ReferenceEquals(elementType, StringType) && enumerable is string[] stringArray) + { + output.WriteVarUInt((uint)stringArray.Length); + for (var i = 0; i < stringArray.Length; i++) + { + var s = stringArray[i]; + if (s == null) + output.WriteByte(BinaryTypeCode.Null); + else + WriteString(s, output, context); + } + return true; + } + + // All other primitive arrays don't need context — dispatch through base class + return TryWritePrimitiveArrayCore(enumerable, elementType, output); + } + + /// + /// Non-generic core for primitive array writes. Only compiled once by JIT (not per TOutput). + /// Virtual dispatch on BinaryOutputBase is negligible: one vtable lookup per array, not per element. + /// + private static bool TryWritePrimitiveArrayCore(IEnumerable enumerable, Type elementType, BinaryOutputBase output) { // Int32 array - very common case if (ReferenceEquals(elementType, IntType) && enumerable is int[] intArray) { - context.WriteVarUInt((uint)intArray.Length); - context.WriteInt32ArrayOptimized(intArray); + output.WriteVarUInt((uint)intArray.Length); + output.WriteInt32ArrayOptimized(intArray); return true; } // Double array - bulk write as raw bytes if (ReferenceEquals(elementType, DoubleType) && enumerable is double[] doubleArray) { - context.WriteVarUInt((uint)doubleArray.Length); - context.WriteDoubleArrayBulk(doubleArray); + output.WriteVarUInt((uint)doubleArray.Length); + output.WriteDoubleArrayBulk(doubleArray); return true; } // Long array if (ReferenceEquals(elementType, LongType) && enumerable is long[] longArray) { - context.WriteVarUInt((uint)longArray.Length); - context.WriteLongArrayOptimized(longArray); + output.WriteVarUInt((uint)longArray.Length); + output.WriteLongArrayOptimized(longArray); return true; } // Float array - bulk write as raw bytes if (ReferenceEquals(elementType, FloatType) && enumerable is float[] floatArray) { - context.WriteVarUInt((uint)floatArray.Length); - context.WriteFloatArrayBulk(floatArray); + output.WriteVarUInt((uint)floatArray.Length); + output.WriteFloatArrayBulk(floatArray); return true; } // Bool array - pack as bytes if (ReferenceEquals(elementType, BoolType) && enumerable is bool[] boolArray) { - context.WriteVarUInt((uint)boolArray.Length); + output.WriteVarUInt((uint)boolArray.Length); for (var i = 0; i < boolArray.Length; i++) { - context.WriteByte(boolArray[i] ? BinaryTypeCode.True : BinaryTypeCode.False); + output.WriteByte(boolArray[i] ? BinaryTypeCode.True : BinaryTypeCode.False); } - context.WriteVarUInt((uint)boolArray.Length); + output.WriteVarUInt((uint)boolArray.Length); return true; } // Guid array - bulk write if (ReferenceEquals(elementType, GuidType) && enumerable is Guid[] guidArray) { - context.WriteVarUInt((uint)guidArray.Length); - context.WriteGuidArrayBulk(guidArray); + output.WriteVarUInt((uint)guidArray.Length); + output.WriteGuidArrayBulk(guidArray); return true; } // Decimal array if (ReferenceEquals(elementType, DecimalType) && enumerable is decimal[] decimalArray) { - context.WriteVarUInt((uint)decimalArray.Length); + output.WriteVarUInt((uint)decimalArray.Length); for (var i = 0; i < decimalArray.Length; i++) { - WriteDecimalUnsafe(decimalArray[i], context); + WriteDecimalUnsafe(decimalArray[i], output); } return true; } @@ -1476,25 +1553,11 @@ public static partial class AcBinarySerializer // DateTime array if (ReferenceEquals(elementType, DateTimeType) && enumerable is DateTime[] dateTimeArray) { - context.WriteVarUInt((uint)dateTimeArray.Length); + output.WriteVarUInt((uint)dateTimeArray.Length); for (var i = 0; i < dateTimeArray.Length; i++) { - WriteDateTimeUnsafe(dateTimeArray[i], context); - } - return true; - } + WriteDateTimeUnsafe(dateTimeArray[i], output); - // String array - common case - if (ReferenceEquals(elementType, StringType) && enumerable is string[] stringArray) - { - context.WriteVarUInt((uint)stringArray.Length); - for (var i = 0; i < stringArray.Length; i++) - { - var s = stringArray[i]; - if (s == null) - context.WriteByte(BinaryTypeCode.Null); - else - WriteString(s, context); } return true; } @@ -1502,10 +1565,12 @@ public static partial class AcBinarySerializer return false; } - private static void WriteDictionary(IDictionary dictionary, BinarySerializationContext context, int depth) + private static void WriteDictionary(IDictionary dictionary, BinarySerializationContext context, int depth) + where TOutput : BinaryOutputBase { - context.WriteByte(BinaryTypeCode.Dictionary); - context.WriteVarUInt((uint)dictionary.Count); + var output = context.Output; + output.WriteByte(BinaryTypeCode.Dictionary); + output.WriteVarUInt((uint)dictionary.Count); var nextDepth = depth + 1; foreach (DictionaryEntry entry in dictionary) diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 624babf..f52489b 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// allowing the deserializer to match properties by name between different types. /// Default: false (no overhead) /// - public bool UseMetadata { get; set; } = true; + public bool UseMetadata { get; set; } = false; /// /// When true, checks for duplicate property name hashes during serialization (UseMetadata mode). diff --git a/AyCode.Core/Serializers/Binaries/ArrayBinaryOutput.cs b/AyCode.Core/Serializers/Binaries/ArrayBinaryOutput.cs index b205d12..c8b5e80 100644 --- a/AyCode.Core/Serializers/Binaries/ArrayBinaryOutput.cs +++ b/AyCode.Core/Serializers/Binaries/ArrayBinaryOutput.cs @@ -398,6 +398,21 @@ public sealed class ArrayBinaryOutput : BinaryOutputBase, IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() => _position = 0; + /// + /// Detaches the internal buffer as a BinarySerializationResult and allocates a fresh buffer. + /// The caller owns the returned result and must dispose it to return the buffer to the pool. + /// + public AcBinarySerializer.BinarySerializationResult DetachResult() + { + var resultBuffer = _buffer; + var resultLength = _position; + + _buffer = ArrayPool.Shared.Rent(Math.Max(resultBuffer.Length / 2, MinBufferSize)); + _position = 0; + + return new AcBinarySerializer.BinarySerializationResult(resultBuffer, resultLength, pooled: true); + } + #endregion #region IDisposable diff --git a/AyCode.Core/Serializers/Binaries/BinaryOutputBase.cs b/AyCode.Core/Serializers/Binaries/BinaryOutputBase.cs index 95aaeec..829b220 100644 --- a/AyCode.Core/Serializers/Binaries/BinaryOutputBase.cs +++ b/AyCode.Core/Serializers/Binaries/BinaryOutputBase.cs @@ -47,82 +47,26 @@ public abstract class BinaryOutputBase : IBinaryOutput #endregion - #region Virtual — WriteTypeCodeAndRaw + #region Abstract — WriteTypeCodeAndRaw /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public virtual void WriteTypeCodeAndRaw(byte typeCode, T value) where T : unmanaged - { - EnsureCapacity(1 + Unsafe.SizeOf()); - WriteByte(typeCode); - WriteRaw(value); - } + public abstract void WriteTypeCodeAndRaw(byte typeCode, T value) where T : unmanaged; #endregion - #region Virtual — VarInt Encoding + #region Abstract — VarInt Encoding /// - public virtual void WriteVarInt(int value) - { - var encoded = (uint)((value << 1) ^ (value >> 31)); - WriteVarUInt(encoded); - } + public abstract void WriteVarInt(int value); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public virtual void WriteVarUInt(uint value) - { - if (value < 0x80) - { - WriteByte((byte)value); - return; - } - - WriteVarUIntMultiByte(value); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void WriteVarUIntMultiByte(uint value) - { - while (value >= 0x80) - { - WriteByte((byte)(value | 0x80)); - value >>= 7; - } - WriteByte((byte)value); - } + public abstract void WriteVarUInt(uint value); /// - public virtual void WriteVarLong(long value) - { - var encoded = (ulong)((value << 1) ^ (value >> 63)); - WriteVarULong(encoded); - } + public abstract void WriteVarLong(long value); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public virtual void WriteVarULong(ulong value) - { - if (value < 0x80) - { - WriteByte((byte)value); - return; - } - - WriteVarULongMultiByte(value); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void WriteVarULongMultiByte(ulong value) - { - while (value >= 0x80) - { - WriteByte((byte)(value | 0x80)); - value >>= 7; - } - WriteByte((byte)value); - } + public abstract void WriteVarULong(ulong value); #endregion @@ -137,11 +81,7 @@ public abstract class BinaryOutputBase : IBinaryOutput } /// - public virtual void WriteDateTimeBits(DateTime value) - { - WriteRaw(value.Ticks); - WriteByte((byte)value.Kind); - } + public abstract void WriteDateTimeBits(DateTime value); /// public virtual void WriteGuidBits(Guid value) @@ -152,11 +92,7 @@ public abstract class BinaryOutputBase : IBinaryOutput } /// - public virtual void WriteDateTimeOffsetBits(DateTimeOffset value) - { - WriteRaw(value.UtcTicks); - WriteRaw((short)value.Offset.TotalMinutes); - } + public abstract void WriteDateTimeOffsetBits(DateTimeOffset value); #endregion @@ -247,39 +183,16 @@ public abstract class BinaryOutputBase : IBinaryOutput #endregion - #region Virtual — Bulk Array Writes (overridable for optimization) + #region Bulk Array Writes /// - public virtual void WriteDoubleArrayBulk(double[] array) - { - for (var i = 0; i < array.Length; i++) - { - WriteByte(BinaryTypeCode.Float64); - WriteRaw(array[i]); - } - } + public abstract void WriteDoubleArrayBulk(double[] array); /// - public virtual void WriteFloatArrayBulk(float[] array) - { - for (var i = 0; i < array.Length; i++) - { - WriteByte(BinaryTypeCode.Float32); - WriteRaw(array[i]); - } - } + public abstract void WriteFloatArrayBulk(float[] array); /// - public virtual void WriteGuidArrayBulk(Guid[] array) - { - Span buf = stackalloc byte[16]; - for (var i = 0; i < array.Length; i++) - { - WriteByte(BinaryTypeCode.Guid); - array[i].TryWriteBytes(buf); - WriteBytes(buf); - } - } + public abstract void WriteGuidArrayBulk(Guid[] array); /// public virtual void WriteInt32ArrayOptimized(int[] array)