diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
index 4d88643..699838e 100644
--- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
@@ -1,779 +1,415 @@
-//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;
-
-/////
-///// 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);
-
-// // Combine with compilation
-// var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
-
-// // 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;
-
-// var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
-// ? string.Empty
-// : typeSymbol.ContainingNamespace.ToDisplayString();
-
-// var className = typeSymbol.Name;
-// var fullTypeName = typeSymbol.ToDisplayString();
-// var isStruct = typeSymbol.IsValueType;
-
-// // 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;
-
-// // 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));
-
-// 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;
-
-// while (current != null)
-// {
-// parts.Insert(0, current.Name);
-// current = current.ContainingType;
-// }
-
-// 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 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);
-// }
-
-// 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();
-
-// 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;
-// }
-
-// 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;
-// }
-// }
-
-// return false;
-// }
-
-// private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context)
-// {
-// if (classes.IsDefaultOrEmpty)
-// return;
-
-// 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));
-// }
-// }
-
-// 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();
-
-// if (!string.IsNullOrEmpty(classInfo.Namespace))
-// {
-// sb.AppendLine($"namespace {classInfo.Namespace}");
-// sb.AppendLine("{");
-// }
-
-// 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}{{");
-
-// // 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 Serialize method
-// GenerateSerializeMethod(sb, classInfo, indent);
-
-// // Generate Deserialize method
-// GenerateDeserializeMethod(sb, classInfo, indent);
-
-// sb.AppendLine($"{indent}}}");
-
-// if (!string.IsNullOrEmpty(classInfo.Namespace))
-// {
-// sb.AppendLine("}");
-// }
-
-// 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, 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 + " ");
-// }
-
-// sb.AppendLine($"{indent} }}");
-// sb.AppendLine();
-// }
-
-// 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;
-// }
-
-// 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;
-// }
-
-// // 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);
-// }
-
-// ///
-// /// 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;
-
-// 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.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.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.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.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.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.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.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;
-
-// 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} {{");
-
-// foreach (var prop in classInfo.Properties)
-// {
-// GenerateDeserializeProperty(sb, prop, 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();");
-
-// // 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 actual value
-// sb.AppendLine($"{indent} else");
-// sb.AppendLine($"{indent} {{");
-// GenerateDeserializeValue(sb, prop, indent + " ");
-// sb.AppendLine($"{indent} }}");
-
-// sb.AppendLine($"{indent}}}");
-// }
-
-// 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;
-
-// 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.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.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.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.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.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.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.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.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;
-// }
-// }
-//}
-
-/////
-///// 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;
-// }
-//}
-
-/////
-///// 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;
-// }
-//}
-
-/////
-///// 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
-//}
+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;
+
+///
+/// Generates IGeneratedBinaryWriter implementations for [AcBinarySerializable] types.
+/// Also generates a ModuleInitializer that auto-registers all writers at startup.
+///
+[Generator]
+public class AcBinarySourceGenerator : IIncrementalGenerator
+{
+ private const string AttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var classDeclarations = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AttributeName,
+ predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax,
+ transform: static (ctx, _) => GetClassInfo(ctx))
+ .Where(static info => info != null);
+
+ context.RegisterSourceOutput(classDeclarations.Collect(),
+ static (spc, classes) => Execute(classes!, spc));
+ }
+
+ private static SerializableClassInfo? GetClassInfo(GeneratorAttributeSyntaxContext context)
+ {
+ if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
+ return null;
+
+ // Skip nested types — generated writer class can't be placed inside containing type
+ if (typeSymbol.ContainingType != null)
+ return null;
+
+ var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
+ ? string.Empty
+ : typeSymbol.ContainingNamespace.ToDisplayString();
+
+ 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)
+ {
+ var hasIgnore = p.GetAttributes().Any(a =>
+ {
+ var name = a.AttributeClass?.Name ?? "";
+ return name == "JsonIgnoreAttribute" || name == "IgnoreMemberAttribute" || name == "BsonIgnoreAttribute";
+ });
+ if (hasIgnore) continue;
+
+ properties.Add(new PropInfo(
+ p.Name,
+ p.Type.ToDisplayString(),
+ GetKind(p.Type),
+ p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type)));
+ }
+ }
+
+ // IId: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering
+ var isIId = typeSymbol.AllInterfaces.Any(i =>
+ i.IsGenericType &&
+ i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId");
+
+ if (isIId)
+ properties.Sort((a, b) =>
+ {
+ var aIsId = a.Name == "Id" ? 0 : 1;
+ var bIsId = b.Name == "Id" ? 0 : 1;
+ if (aIsId != bIsId) return aIsId.CompareTo(bIsId);
+ return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
+ });
+ else
+ properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
+
+ return new SerializableClassInfo(namespaceName, typeSymbol.Name, typeSymbol.ToDisplayString(), properties);
+ }
+
+ private static void Execute(ImmutableArray classes, SourceProductionContext context)
+ {
+ if (classes.IsDefaultOrEmpty) return;
+ var valid = classes.Where(c => c != null).Cast().ToList();
+ if (valid.Count == 0) return;
+
+ foreach (var ci in valid)
+ context.AddSource($"{ci.ClassName}_GeneratedWriter.g.cs", SourceText.From(GenWriter(ci), Encoding.UTF8));
+
+ context.AddSource("AcBinaryGeneratedWriters_Init.g.cs", SourceText.From(GenInit(valid), Encoding.UTF8));
+ }
+
+ private static string GenWriter(SerializableClassInfo ci)
+ {
+ var sb = new StringBuilder(2048);
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine("using System.Runtime.CompilerServices;");
+ sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
+ sb.AppendLine();
+ if (!string.IsNullOrEmpty(ci.Namespace))
+ sb.AppendLine($"namespace {ci.Namespace};");
+ sb.AppendLine();
+ sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedWriter : IGeneratedBinaryWriter");
+ sb.AppendLine("{");
+ sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
+ sb.AppendLine();
+ sb.AppendLine(" public void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context, int depth) where TOutput : struct, IBinaryOutputBase");
+ sb.AppendLine(" {");
+ sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
+
+ foreach (var p in ci.Properties)
+ {
+ sb.AppendLine();
+ EmitProp(sb, p, " ");
+ }
+
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+
+ private static void EmitProp(StringBuilder sb, PropInfo p, string i)
+ {
+ var a = $"obj.{p.Name}";
+
+ // Nullable value types always use markered path (need Null marker)
+ if (IsNullableVTKind(p.TypeKind))
+ {
+ sb.AppendLine($"{i}if ({a}.HasValue)");
+ sb.AppendLine($"{i}{{");
+ EmitVal(sb, Underlying(p.TypeKind), $"{a}.Value", i + " ");
+ sb.AppendLine($"{i}}}");
+ sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.PropertySkip);");
+ return;
+ }
+
+ // Markerless types: write raw value only, no type marker, no PropertySkip
+ // Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
+ if (IsMarkerless(p.TypeKind))
+ {
+ EmitMarkerless(sb, p.TypeKind, a, i);
+ return;
+ }
+
+ // Non-markerless types: write WITH type marker byte (markered path)
+ switch (p.TypeKind)
+ {
+ case PropertyTypeKind.String:
+ sb.AppendLine($"{i}AcBinarySerializer.WriteStringGenerated({a}, context);");
+ break;
+ case PropertyTypeKind.Complex:
+ case PropertyTypeKind.Collection:
+ if (p.IsNullable)
+ {
+ sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);");
+ }
+ else
+ sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);");
+ break;
+ default:
+ EmitSkip(sb, p.TypeKind, a, i);
+ break;
+ }
+ }
+
+ ///
+ /// Returns true for property types that use markerless serialization in FastMode.
+ /// These types have ExpectedTypeCode at runtime — no type marker byte, no PropertySkip for defaults.
+ ///
+ private static bool IsMarkerless(PropertyTypeKind k) => k switch
+ {
+ PropertyTypeKind.Int32 or PropertyTypeKind.Int64 or PropertyTypeKind.Int16 or
+ PropertyTypeKind.Byte or PropertyTypeKind.UInt16 or PropertyTypeKind.UInt32 or PropertyTypeKind.UInt64 or
+ PropertyTypeKind.Double or PropertyTypeKind.Single or PropertyTypeKind.Decimal or
+ PropertyTypeKind.DateTime or PropertyTypeKind.Guid or
+ PropertyTypeKind.TimeSpan or PropertyTypeKind.DateTimeOffset => true,
+ _ => false
+ };
+
+ ///
+ /// Emits raw value only — no type marker, no PropertySkip.
+ /// Matches runtime WritePropertyMarkerless exactly.
+ ///
+ private static void EmitMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
+ {
+ switch (k)
+ {
+ case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteVarInt({a});"); break;
+ case PropertyTypeKind.Int64: sb.AppendLine($"{i}context.WriteVarLong({a});"); break;
+ case PropertyTypeKind.Double: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
+ case PropertyTypeKind.Single: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
+ case PropertyTypeKind.Decimal: sb.AppendLine($"{i}context.WriteDecimalBits({a});"); break;
+ case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteDateTimeBits({a});"); break;
+ case PropertyTypeKind.Guid: sb.AppendLine($"{i}context.WriteGuidBits({a});"); break;
+ case PropertyTypeKind.Byte: sb.AppendLine($"{i}context.WriteByte({a});"); break;
+ case PropertyTypeKind.Int16: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
+ case PropertyTypeKind.UInt16: sb.AppendLine($"{i}context.WriteRaw({a});"); break;
+ case PropertyTypeKind.UInt32: sb.AppendLine($"{i}context.WriteVarUInt({a});"); break;
+ case PropertyTypeKind.UInt64: sb.AppendLine($"{i}context.WriteVarULong({a});"); break;
+ case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}context.WriteRaw({a}.Ticks);"); break;
+ case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}context.WriteDateTimeOffsetBits({a});"); break;
+ }
+ }
+
+ private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string i)
+ {
+ switch (k)
+ {
+ case PropertyTypeKind.Int32:
+ sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}");
+ break;
+ case PropertyTypeKind.Int64:
+ sb.AppendLine($"{i}if ({a} == 0L) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}");
+ break;
+ case PropertyTypeKind.Boolean:
+ sb.AppendLine($"{i}if (!{a}) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.True);");
+ break;
+ case PropertyTypeKind.Double:
+ sb.AppendLine($"{i}if ({a} == 0.0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a}); }}");
+ break;
+ case PropertyTypeKind.Single:
+ sb.AppendLine($"{i}if ({a} == 0f) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a}); }}");
+ break;
+ case PropertyTypeKind.Decimal:
+ sb.AppendLine($"{i}if ({a} == 0m) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a}); }}");
+ break;
+ case PropertyTypeKind.DateTime:
+ sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});");
+ break;
+ case PropertyTypeKind.Guid:
+ sb.AppendLine($"{i}if ({a} == System.Guid.Empty) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a}); }}");
+ break;
+ case PropertyTypeKind.Byte:
+ sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt8); context.WriteByte({a}); }}");
+ break;
+ case PropertyTypeKind.Int16:
+ sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int16); context.WriteRaw({a}); }}");
+ break;
+ case PropertyTypeKind.UInt16:
+ sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt16); context.WriteRaw({a}); }}");
+ break;
+ case PropertyTypeKind.UInt32:
+ sb.AppendLine($"{i}if ({a} == 0U) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt32); context.WriteVarUInt({a}); }}");
+ break;
+ case PropertyTypeKind.UInt64:
+ sb.AppendLine($"{i}if ({a} == 0UL) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.UInt64); context.WriteVarULong({a}); }}");
+ break;
+ case PropertyTypeKind.Enum:
+ var s = a.Replace(".", "_");
+ sb.AppendLine($"{i}var ev_{s} = (int){a};");
+ sb.AppendLine($"{i}if (ev_{s} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt(ev_{s}, out var te_{s})) {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(te_{s}); }}");
+ sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Enum); context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(ev_{s}); }}");
+ break;
+ case PropertyTypeKind.TimeSpan:
+ sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.TimeSpan); context.WriteRaw({a}.Ticks);");
+ break;
+ case PropertyTypeKind.DateTimeOffset:
+ sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});");
+ break;
+ default:
+ sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);");
+ break;
+ }
+ }
+
+ private static void EmitVal(StringBuilder sb, PropertyTypeKind k, string a, string i)
+ {
+ switch (k)
+ {
+ case PropertyTypeKind.Int32: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a});"); break;
+ case PropertyTypeKind.Int64: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a});"); break;
+ case PropertyTypeKind.Boolean: sb.AppendLine($"{i}context.WriteByte({a} ? BinaryTypeCode.True : BinaryTypeCode.False);"); break;
+ case PropertyTypeKind.Double: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a});"); break;
+ case PropertyTypeKind.Single: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a});"); break;
+ case PropertyTypeKind.Decimal: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a});"); break;
+ case PropertyTypeKind.DateTime: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});"); break;
+ case PropertyTypeKind.Guid: sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a});"); break;
+ default: EmitSkip(sb, k, a, i); break;
+ }
+ }
+
+ private static string GenInit(List classes)
+ {
+ var sb = new StringBuilder(512);
+ sb.AppendLine("// ");
+ sb.AppendLine("using System.Runtime.CompilerServices;");
+ sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
+ sb.AppendLine();
+ sb.AppendLine("namespace AyCode.Core.Serializers.Generated;");
+ sb.AppendLine();
+ sb.AppendLine("internal static class AcBinaryGeneratedWritersInit");
+ sb.AppendLine("{");
+ sb.AppendLine(" [ModuleInitializer]");
+ sb.AppendLine(" internal static void Register()");
+ sb.AppendLine(" {");
+ foreach (var ci in classes)
+ sb.AppendLine($" AcBinarySerializer.RegisterGeneratedWriter(typeof({ci.FullTypeName}), {ci.FullTypeName}_GeneratedWriter.Instance);");
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+
+ #region Type analysis
+
+ private static bool IsNullableVT(ITypeSymbol t) =>
+ t is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
+
+ private static PropertyTypeKind GetKind(ITypeSymbol type)
+ {
+ if (type is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
+ return GetKindCore(n.TypeArguments[0], true);
+ return GetKindCore(type, false);
+ }
+
+ private static PropertyTypeKind GetKindCore(ITypeSymbol type, bool nullable)
+ {
+ switch (type.SpecialType)
+ {
+ case SpecialType.System_String: return PropertyTypeKind.String;
+ case SpecialType.System_Int32: return nullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32;
+ case SpecialType.System_Int64: return nullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64;
+ case SpecialType.System_Int16: return nullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16;
+ case SpecialType.System_Byte: return nullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte;
+ case SpecialType.System_UInt16: return nullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16;
+ case SpecialType.System_UInt32: return nullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32;
+ case SpecialType.System_UInt64: return nullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64;
+ case SpecialType.System_Boolean: return nullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean;
+ case SpecialType.System_Single: return nullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single;
+ case SpecialType.System_Double: return nullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double;
+ case SpecialType.System_Decimal: return nullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal;
+ case SpecialType.System_DateTime: return nullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime;
+ default: break;
+ }
+ var fn = type.ToDisplayString();
+ if (fn == "System.Guid") return nullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid;
+ if (fn == "System.TimeSpan") return nullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan;
+ if (fn == "System.DateTimeOffset") return nullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset;
+ if (type.TypeKind == TypeKind.Enum) return nullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum;
+ if (type is IArrayTypeSymbol) return PropertyTypeKind.Collection;
+ if (type is INamedTypeSymbol nt && nt.AllInterfaces.Any(iface => iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T))
+ return PropertyTypeKind.Collection;
+ if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex;
+ return PropertyTypeKind.Unknown;
+ }
+
+ private static bool IsNullableVTKind(PropertyTypeKind k) => k >= PropertyTypeKind.NullableInt32;
+
+ private static PropertyTypeKind Underlying(PropertyTypeKind k) => k switch
+ {
+ PropertyTypeKind.NullableInt32 => PropertyTypeKind.Int32, PropertyTypeKind.NullableInt64 => PropertyTypeKind.Int64,
+ PropertyTypeKind.NullableInt16 => PropertyTypeKind.Int16, PropertyTypeKind.NullableByte => PropertyTypeKind.Byte,
+ PropertyTypeKind.NullableUInt16 => PropertyTypeKind.UInt16, PropertyTypeKind.NullableUInt32 => PropertyTypeKind.UInt32,
+ PropertyTypeKind.NullableUInt64 => PropertyTypeKind.UInt64, PropertyTypeKind.NullableBoolean => PropertyTypeKind.Boolean,
+ PropertyTypeKind.NullableSingle => PropertyTypeKind.Single, PropertyTypeKind.NullableDouble => PropertyTypeKind.Double,
+ PropertyTypeKind.NullableDecimal => PropertyTypeKind.Decimal, PropertyTypeKind.NullableDateTime => PropertyTypeKind.DateTime,
+ PropertyTypeKind.NullableDateTimeOffset => PropertyTypeKind.DateTimeOffset, PropertyTypeKind.NullableTimeSpan => PropertyTypeKind.TimeSpan,
+ PropertyTypeKind.NullableGuid => PropertyTypeKind.Guid, PropertyTypeKind.NullableEnum => PropertyTypeKind.Enum,
+ _ => PropertyTypeKind.Unknown
+ };
+
+ #endregion
+}
+
+internal sealed class SerializableClassInfo
+{
+ public string Namespace { get; }
+ public string ClassName { get; }
+ public string FullTypeName { get; }
+ public List Properties { get; }
+ public SerializableClassInfo(string ns, string cn, string ftn, List p)
+ { Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; }
+}
+
+internal sealed class PropInfo
+{
+ public string Name { get; }
+ public string TypeName { get; }
+ public PropertyTypeKind TypeKind { get; }
+ public bool IsNullable { get; }
+ public PropInfo(string n, string tn, PropertyTypeKind tk, bool nullable)
+ { Name = n; TypeName = tn; TypeKind = tk; IsNullable = nullable; }
+}
+
+internal enum PropertyTypeKind
+{
+ Unknown, String, Int32, Int64, Int16, Byte, UInt16, UInt32, UInt64,
+ Boolean, Single, Double, Decimal, DateTime, DateTimeOffset, TimeSpan, Guid, Enum,
+ Collection, Complex,
+ NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableUInt16, NullableUInt32, NullableUInt64,
+ NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime,
+ NullableDateTimeOffset, NullableTimeSpan, NullableGuid, NullableEnum
+}
diff --git a/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs b/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs
new file mode 100644
index 0000000..779dee3
--- /dev/null
+++ b/AyCode.Core.Tests/GeneratedWriters/TestOrderWriter.cs
@@ -0,0 +1,141 @@
+using System.Runtime.CompilerServices;
+using AyCode.Core.Serializers.Binaries;
+using AyCode.Core.Tests.TestModels;
+
+namespace AyCode.Core.Tests.GeneratedWriters;
+
+///
+/// Hand-written generated binary writer for TestOrder.
+/// Demonstrates the pattern that the source generator will produce.
+///
+/// Bypasses the runtime switch/delegate property loop:
+/// - Direct obj.Property access instead of Func<>.Invoke()
+/// - No switch dispatch per property
+/// - No boxing for value types
+/// - Small method (~500B native) vs 27KB WriteObject — better ICache
+///
+/// Properties are written in alphabetical order to match the runtime serializer.
+/// Complex/Collection properties fall back to the runtime serializer via WriteValue.
+///
+internal sealed class TestOrderWriter : IGeneratedBinaryWriter
+{
+ internal static readonly TestOrderWriter Instance = new();
+
+ public void WriteProperties(
+ object value,
+ AcBinarySerializer.BinarySerializationContext context,
+ int depth)
+ where TOutput : struct, IBinaryOutputBase
+ {
+ var obj = Unsafe.As(value);
+ var nextDepth = depth;
+
+ // Properties in alphabetical order (matching runtime serializer):
+
+ // AuditMetadata: MetadataInfo? (complex, nullable)
+ WriteComplexOrNull(obj.AuditMetadata, context, nextDepth);
+
+ // Category: SharedCategory? (complex, nullable)
+ WriteComplexOrNull(obj.Category, context, nextDepth);
+
+ // CreatedAt: DateTime
+ context.WriteByte(BinaryTypeCode.DateTime);
+ context.WriteDateTimeBits(obj.CreatedAt);
+
+ // Id: int
+ WriteInt32OrSkip(obj.Id, context);
+
+ // Items: List (collection)
+ WriteComplexOrNull(obj.Items, context, nextDepth);
+
+ // MetadataList: List (collection)
+ WriteComplexOrNull(obj.MetadataList, context, nextDepth);
+
+ // NoMergeItems: List (collection)
+ WriteComplexOrNull(obj.NoMergeItems, context, nextDepth);
+
+ // OrderMetadata: MetadataInfo? (complex, nullable)
+ WriteComplexOrNull(obj.OrderMetadata, context, nextDepth);
+
+ // OrderNumber: string
+ AcBinarySerializer.WriteStringGenerated(obj.OrderNumber, context);
+
+ // Owner: SharedUser? (complex, nullable)
+ WriteComplexOrNull(obj.Owner, context, nextDepth);
+
+ // PaidDateUtc: DateTime? (nullable)
+ var paidDate = obj.PaidDateUtc;
+ if (paidDate.HasValue)
+ {
+ context.WriteByte(BinaryTypeCode.DateTime);
+ context.WriteDateTimeBits(paidDate.Value);
+ }
+ else
+ {
+ context.WriteByte(BinaryTypeCode.Null);
+ }
+
+ // PrimaryTag: SharedTag? (complex, nullable)
+ WriteComplexOrNull(obj.PrimaryTag, context, nextDepth);
+
+ // SecondaryTag: SharedTag? (complex, nullable)
+ WriteComplexOrNull(obj.SecondaryTag, context, nextDepth);
+
+ // Status: TestStatus (enum)
+ context.WriteByte(BinaryTypeCode.Enum);
+ var enumVal = (int)obj.Status;
+ if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))
+ context.WriteByte(tinyEnum);
+ else
+ {
+ context.WriteByte(BinaryTypeCode.Int32);
+ context.WriteVarInt(enumVal);
+ }
+
+ // Tags: List (collection)
+ WriteComplexOrNull(obj.Tags, context, nextDepth);
+
+ // TotalAmount: decimal
+ if (obj.TotalAmount == 0m)
+ context.WriteByte(BinaryTypeCode.PropertySkip);
+ else
+ {
+ context.WriteByte(BinaryTypeCode.Decimal);
+ context.WriteDecimalBits(obj.TotalAmount);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteInt32OrSkip(int value, AcBinarySerializer.BinarySerializationContext context)
+ where TOutput : struct, IBinaryOutputBase
+ {
+ if (value == 0)
+ {
+ context.WriteByte(BinaryTypeCode.PropertySkip);
+ return;
+ }
+
+ if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny))
+ {
+ context.WriteByte(BinaryTypeCode.Int32);
+ context.WriteByte(tiny);
+ return;
+ }
+
+ context.WriteByte(BinaryTypeCode.Int32);
+ context.WriteVarInt(value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteComplexOrNull(object? value, AcBinarySerializer.BinarySerializationContext context, int depth)
+ where TOutput : struct, IBinaryOutputBase
+ {
+ if (value == null)
+ {
+ context.WriteByte(BinaryTypeCode.PropertySkip);
+ return;
+ }
+
+ AcBinarySerializer.WriteValueGenerated(value, value.GetType(), context, depth);
+ }
+}
diff --git a/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs b/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs
index 40ac6de..b13c155 100644
--- a/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs
+++ b/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs
@@ -4,98 +4,37 @@ using AyCode.Core.Tests.TestModels;
namespace AyCode.Core.Tests.Serialization;
///
-/// Tests for Source Generator based serialization integration.
+/// Tests for Source Generator based IGeneratedBinaryWriter integration.
///
[TestClass]
public class GeneratedSerializerIntegrationTests
{
[TestMethod]
- public void GeneratedSerializerType_Exists_ForMarkedTypes()
+ public void GeneratedWriterType_Exists_ForMarkedTypes()
{
- // Arrange - types marked with [AcBinarySerializable]
var type = typeof(GeneratedSerializerTestModel);
-
- // Act - find the generated serializer type directly
- var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
- var serializerType = type.Assembly.GetType(generatedTypeName);
-
- // Assert
- Assert.IsNotNull(serializerType,
- $"Generated serializer type '{generatedTypeName}' should exist for [AcBinarySerializable] marked type");
+ var writerTypeName = $"{type.FullName}_GeneratedWriter";
+ var writerType = type.Assembly.GetType(writerTypeName);
+
+ Assert.IsNotNull(writerType,
+ $"Generated writer type '{writerTypeName}' should exist for [AcBinarySerializable] marked type");
}
[TestMethod]
- public void GeneratedSerializerType_HasCorrectMethods()
+ public void GeneratedWriterType_ImplementsInterface()
{
- // Arrange
var type = typeof(SimpleGeneratedModel);
-
- // Act - find the generated serializer type directly
- var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
- var serializerType = type.Assembly.GetType(generatedTypeName);
-
- // Assert
- Assert.IsNotNull(serializerType, $"Generated serializer type '{generatedTypeName}' should exist");
-
- var serializeMethod = serializerType.GetMethod("Serialize",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- Assert.IsNotNull(serializeMethod, "Serialize method should exist");
-
- var deserializeMethod = serializerType.GetMethod("Deserialize",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- Assert.IsNotNull(deserializeMethod, "Deserialize method should exist");
-
- var propertyNamesField = serializerType.GetField("PropertyNames",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- Assert.IsNotNull(propertyNamesField, "PropertyNames field should exist");
-
- var propertyNames = propertyNamesField.GetValue(null) as string[];
- Assert.IsNotNull(propertyNames, "PropertyNames should not be null");
- Assert.AreEqual(3, propertyNames.Length, "SimpleGeneratedModel has 3 properties");
-
- // Verify alphabetical order
- Assert.AreEqual("Age", propertyNames[0]);
- Assert.AreEqual("FirstName", propertyNames[1]);
- Assert.AreEqual("LastName", propertyNames[2]);
+ var writerTypeName = $"{type.FullName}_GeneratedWriter";
+ var writerType = type.Assembly.GetType(writerTypeName);
+
+ Assert.IsNotNull(writerType, $"Generated writer type '{writerTypeName}' should exist");
+ Assert.IsTrue(typeof(IGeneratedBinaryWriter).IsAssignableFrom(writerType),
+ "Generated writer should implement IGeneratedBinaryWriter");
}
[TestMethod]
- public void GeneratedSerializerPropertyNames_MatchRuntimeOrder()
+ public void Serialization_WorksCorrectly_WithGeneratedWriterPresent()
{
- // This test verifies that the generated property order matches the runtime serializer's order
- // This is critical for binary compatibility!
-
- var type = typeof(GeneratedSerializerTestModel);
-
- // Get generated property names
- var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
- var serializerType = type.Assembly.GetType(generatedTypeName);
- Assert.IsNotNull(serializerType);
-
- var propertyNamesField = serializerType.GetField("PropertyNames",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
- var generatedNames = propertyNamesField?.GetValue(null) as string[];
- Assert.IsNotNull(generatedNames);
-
- // Get runtime property names using the same logic as TypeMetadataBase
- var runtimeProps = type.GetProperties(
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
- .Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any())
- .OrderBy(p => p.Name, StringComparer.Ordinal)
- .Select(p => p.Name)
- .ToArray();
-
- // Assert they match
- CollectionAssert.AreEqual(runtimeProps, generatedNames,
- "Generated property names must match runtime property order for binary compatibility");
- }
-
- [TestMethod]
- public void Serialization_WorksCorrectly_WithGeneratedSerializerPresent()
- {
- // This test ensures that regular serialization still works even when
- // generated serializers are present (they are not yet integrated into the hot path)
-
var original = new GeneratedSerializerTestModel
{
Id = 42,
@@ -107,12 +46,10 @@ public class GeneratedSerializerIntegrationTests
Price = 99.99m,
BigNumber = 9999999999L
};
-
- // Serialize and deserialize using the regular path
+
var bytes = AcBinarySerializer.Serialize(original, AcBinarySerializerOptions.WithoutReferenceHandling);
var deserialized = AcBinaryDeserializer.Deserialize(bytes);
-
- // Assert
+
Assert.IsNotNull(deserialized);
Assert.AreEqual(original.Id, deserialized.Id);
Assert.AreEqual(original.Name, deserialized.Name);
@@ -125,25 +62,73 @@ public class GeneratedSerializerIntegrationTests
}
[TestMethod]
- public void NestedType_GeneratedSerializer_IsFound()
+ public void GeneratedWriter_PrimitiveClass_RoundTrip()
{
- // Test that nested types (like QuickBenchmark.TestClassWithRepeatedValues)
- // have their generated serializers properly named and discoverable
-
- var type = typeof(QuickBenchmark.TestClassWithRepeatedValues);
- var ns = type.Namespace ?? "";
-
- // For nested types, the generated class is at namespace level with just the type name
- var simpleName = $"{(string.IsNullOrEmpty(ns) ? "" : ns + ".")}{type.Name}_AcBinarySerializer";
- var serializerType = type.Assembly.GetType(simpleName);
-
- Assert.IsNotNull(serializerType,
- $"Generated serializer for nested type should be found at '{simpleName}'");
-
- // Verify it has the expected methods
- Assert.IsNotNull(serializerType.GetMethod("Serialize",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static));
- Assert.IsNotNull(serializerType.GetMethod("Deserialize",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static));
+ var original = new PrimitiveTestClass
+ {
+ IntValue = 42,
+ StringValue = "TestName",
+ BoolValue = true,
+ DoubleValue = 3.14,
+ DateTimeValue = new DateTime(2025, 1, 5, 10, 30, 0, DateTimeKind.Utc),
+ GuidValue = Guid.NewGuid(),
+ DecimalValue = 99.99m,
+ LongValue = 9999999999L,
+ FloatValue = 1.5f,
+ ByteValue = 42,
+ ShortValue = 123,
+ EnumValue = TestStatus.Active,
+ NullableInt = 7,
+ NullableIntNull = null
+ };
+
+ var options = AcBinarySerializerOptions.FastMode;
+ var bytes = AcBinarySerializer.Serialize(original, options);
+ var deserialized = AcBinaryDeserializer.Deserialize(bytes, options);
+
+ Assert.IsNotNull(deserialized);
+ Assert.AreEqual(original.IntValue, deserialized.IntValue);
+ Assert.AreEqual(original.StringValue, deserialized.StringValue);
+ Assert.AreEqual(original.BoolValue, deserialized.BoolValue);
+ Assert.AreEqual(original.DoubleValue, deserialized.DoubleValue);
+ Assert.AreEqual(original.DateTimeValue, deserialized.DateTimeValue);
+ Assert.AreEqual(original.GuidValue, deserialized.GuidValue);
+ Assert.AreEqual(original.DecimalValue, deserialized.DecimalValue);
+ Assert.AreEqual(original.LongValue, deserialized.LongValue);
+ Assert.AreEqual(original.NullableInt, deserialized.NullableInt);
+ Assert.IsNull(deserialized.NullableIntNull);
+ }
+
+ [TestMethod]
+ public void GeneratedWriter_ComplexHierarchy_RoundTrip()
+ {
+ TestDataFactory.ResetIdCounter();
+ var sharedTag = TestDataFactory.CreateTag("SharedTag");
+ var sharedUser = TestDataFactory.CreateUser("shareduser");
+
+ var order = TestDataFactory.CreateOrder(
+ itemCount: 2,
+ palletsPerItem: 2,
+ measurementsPerPallet: 2,
+ pointsPerMeasurement: 2,
+ sharedTag: sharedTag,
+ sharedUser: sharedUser);
+
+ var options = AcBinarySerializerOptions.FastMode;
+ var bytes = AcBinarySerializer.Serialize(order, options);
+ var deserialized = AcBinaryDeserializer.Deserialize(bytes, options);
+
+ Assert.IsNotNull(deserialized);
+ Assert.AreEqual(order.Id, deserialized.Id);
+ Assert.AreEqual(order.OrderNumber, deserialized.OrderNumber);
+ Assert.AreEqual(order.Status, deserialized.Status);
+ Assert.AreEqual(order.TotalAmount, deserialized.TotalAmount);
+ Assert.AreEqual(order.Items.Count, deserialized.Items.Count);
+
+ for (var i = 0; i < order.Items.Count; i++)
+ {
+ Assert.AreEqual(order.Items[i].Id, deserialized.Items[i].Id);
+ Assert.AreEqual(order.Items[i].Pallets.Count, deserialized.Items[i].Pallets.Count);
+ }
}
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
index 5046cd1..37b14a7 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
@@ -218,6 +218,44 @@ public static partial class AcBinarySerializer
}
#endif
+ ///
+ /// Registers a source-generated binary writer for the specified type.
+ /// Once registered, WriteObject bypasses the runtime switch/delegate property loop
+ /// and calls the generated writer directly — eliminating Func<>.Invoke() overhead.
+ /// Call once at startup (e.g., in a static constructor or module initializer).
+ ///
+ /// The type to register the writer for.
+ /// The generated writer instance (typically a singleton).
+ internal static void RegisterGeneratedWriter(IGeneratedBinaryWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+ GeneratedWriterRegistry.Register(typeof(T), writer);
+ }
+
+ ///
+ /// Registers a source-generated binary writer for the specified type.
+ ///
+ internal static void RegisterGeneratedWriter(Type type, IGeneratedBinaryWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+ ArgumentNullException.ThrowIfNull(writer);
+ GeneratedWriterRegistry.Register(type, writer);
+ }
+
+ ///
+ /// Thread-safe registry for generated writers. Looked up once per TypeMetadataWrapper creation.
+ ///
+ internal static class GeneratedWriterRegistry
+ {
+ private static readonly ConcurrentDictionary Writers = new();
+
+ internal static void Register(Type type, IGeneratedBinaryWriter writer) => Writers[type] = writer;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static IGeneratedBinaryWriter? TryGet(Type type) =>
+ Writers.TryGetValue(type, out var writer) ? writer : null;
+ }
+
///
/// Serialize object to binary with default options.
///
@@ -429,6 +467,38 @@ public static partial class AcBinarySerializer
#endregion
+ #region Generated Writer Bridge Methods
+
+ ///
+ /// Bridge for generated writers to call the runtime WriteValue for complex/collection properties.
+ /// Generated writers handle primitives directly; complex types delegate here.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void WriteValueGenerated(object? value, Type type, BinarySerializationContext context, int depth)
+ where TOutput : struct, IBinaryOutputBase
+ {
+ WriteValue(value, type, context, depth);
+ }
+
+ ///
+ /// Bridge for generated writers to call the runtime WriteString.
+ /// Matches WritePropertyOrSkip String case exactly: null → PropertySkip, empty → StringEmpty.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void WriteStringGenerated(string? value, BinarySerializationContext context)
+ where TOutput : struct, IBinaryOutputBase
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ context.WriteByte(value == null ? BinaryTypeCode.PropertySkip : BinaryTypeCode.StringEmpty);
+ return;
+ }
+
+ WriteString(value, context);
+ }
+
+ #endregion
+
#region Value Writing
private static void WriteValue(object? value, Type type, BinarySerializationContext context, int depth)
@@ -1035,6 +1105,16 @@ public static partial class AcBinarySerializer
var propCount = properties.Length;
var hasPropertyFilter = context.HasPropertyFilter;
+ // Source-generated fast path: bypass the entire switch/delegate loop.
+ // Only when no caching features are active (no string interning, no reference handling)
+ // to avoid scan pass / write pass mismatch with interned strings and tracked references.
+ var generatedWriter = wrapper.GeneratedWriter;
+ if (generatedWriter != null && !hasPropertyFilter && !context.UseMetadata && !context.HasCaching)
+ {
+ generatedWriter.WriteProperties(value, context, nextDepth);
+ return;
+ }
+
if (!context.UseMetadata)
{
// Markerless loop: no extra branching per property for the common case.
diff --git a/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs
new file mode 100644
index 0000000..f8389ac
--- /dev/null
+++ b/AyCode.Core/Serializers/Binaries/IGeneratedBinaryWriter.cs
@@ -0,0 +1,29 @@
+using System.Runtime.CompilerServices;
+
+namespace AyCode.Core.Serializers.Binaries;
+
+///
+/// Interface for source-generated binary property writers.
+/// Implementations bypass the runtime switch/delegate property loop in WriteObject.
+/// Each generated writer handles all properties of a specific type using direct obj.Property access.
+///
+/// Performance gains over runtime path:
+/// - No Func<>.Invoke() delegate calls (~5-8ns/property saved)
+/// - No switch dispatch (~2-3ns/property saved)
+/// - No boxing for value type properties
+/// - Small per-type code (~300B) vs 27KB monolithic WriteObject — better ICache behavior
+///
+internal interface IGeneratedBinaryWriter
+{
+ ///
+ /// Writes all properties of the given object to the serialization context.
+ /// Called from WriteObject when a generated writer is available for the type.
+ /// The implementation uses direct property access (obj.Id, obj.Name, etc.) instead of delegates.
+ ///
+ /// The object whose properties to write. Implementation casts to the concrete type.
+ /// The serialization context (owns buffer, position, options).
+ /// Current depth in the object graph (for nested object serialization).
+ /// Output strategy (ArrayBinaryOutput or BufferWriterBinaryOutput).
+ void WriteProperties(object value, AcBinarySerializer.BinarySerializationContext context, int depth)
+ where TOutput : struct, IBinaryOutputBase;
+}
diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
index 693686c..f654c4c 100644
--- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs
+++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
@@ -58,6 +58,13 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat
///
internal TypeMetadataWrapper?[]? PropertyTypeWrappers;
+ ///
+ /// Source-generated binary writer for this type. Bypasses the runtime switch/delegate loop.
+ /// Set via AcBinarySerializer.RegisterGeneratedWriter. Null = use runtime path.
+ /// Checked once per object in WriteObject (not per property).
+ ///
+ internal IGeneratedBinaryWriter? GeneratedWriter;
+
///
/// Options-filtered subset of metadata.ReferenceProperties for the scan pass.
/// Built lazily on first scan pass call, stable during session, cleared in ResetTracking.
@@ -135,6 +142,9 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat
// Pre-allocate PropertyTypeWrappers — eliminates null/resize checks from hot path
if (metadata.ComplexPropertyCount > 0)
PropertyTypeWrappers = new TypeMetadataWrapper?[metadata.ComplexPropertyCount];
+
+ // Lookup generated writer from registry (once per wrapper creation, not per serialization)
+ GeneratedWriter = AcBinarySerializer.GeneratedWriterRegistry.TryGet(metadata.SourceType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]