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)