Refactor SGen: property/object marker bridges, FixObj support
Major refactor of binary serialization codegen and runtime: - Added property writer bridge methods for markerless/metadata paths - Centralized object marker logic via new bridge methods - Simplified SGen output: single bridge call replaces branching - FixObj slot markers now supported in serialization/deserialization - Refactored collection/dictionary element serialization - Removed redundant WritePropertyMarkerless method - Improved tests: use BinaryTypeCode constants, FixObj parsing - Added InternalsVisibleTo for test project access - Annotated TestSimpleClass for SGen support Reduces generated code size, improves maintainability, and ensures correct handling of new binary format features.
This commit is contained in:
parent
c84c26048c
commit
2f99b4e3b7
|
|
@ -665,21 +665,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
if (IsMarkerless(p.TypeKind))
|
||||
{
|
||||
if (!enableMetadata)
|
||||
{
|
||||
// Per-type metadata disabled — always markerless, no branch
|
||||
EmitMarkerless(sb, p.TypeKind, a, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i + " ");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
EmitMarkerless(sb, p.TypeKind, a, i + " ");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
EmitPropertyBridge(sb, p.TypeKind, a, i);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -811,6 +799,38 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits a single bridge method call for markerless property types with enableMetadata=true.
|
||||
/// The bridge method on BinarySerializationContext handles both UseMetadata=true (markered+skip)
|
||||
/// and UseMetadata=false (markerless) paths internally. Replaces 7-11 lines of generated code with 1 line.
|
||||
/// </summary>
|
||||
private static void EmitPropertyBridge(StringBuilder sb, PropertyTypeKind k, string a, string i)
|
||||
{
|
||||
var call = k switch
|
||||
{
|
||||
PropertyTypeKind.Int32 => $"context.WriteInt32Property({a});",
|
||||
PropertyTypeKind.Int64 => $"context.WriteInt64Property({a});",
|
||||
PropertyTypeKind.Boolean => $"context.WriteBoolProperty({a});",
|
||||
PropertyTypeKind.Double => $"context.WriteFloat64Property({a});",
|
||||
PropertyTypeKind.Single => $"context.WriteFloat32Property({a});",
|
||||
PropertyTypeKind.Decimal => $"context.WriteDecimalProperty({a});",
|
||||
PropertyTypeKind.DateTime => $"context.WriteDateTimeProperty({a});",
|
||||
PropertyTypeKind.Guid => $"context.WriteGuidProperty({a});",
|
||||
PropertyTypeKind.Byte => $"context.WriteByteProperty({a});",
|
||||
PropertyTypeKind.Int16 => $"context.WriteInt16Property({a});",
|
||||
PropertyTypeKind.UInt16 => $"context.WriteUInt16Property({a});",
|
||||
PropertyTypeKind.UInt32 => $"context.WriteUInt32Property({a});",
|
||||
PropertyTypeKind.UInt64 => $"context.WriteUInt64Property({a});",
|
||||
PropertyTypeKind.Enum => $"context.WriteEnumInt32Property((int){a});",
|
||||
PropertyTypeKind.TimeSpan => $"context.WriteTimeSpanProperty({a});",
|
||||
PropertyTypeKind.DateTimeOffset => $"context.WriteDateTimeOffsetProperty({a});",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (call != null)
|
||||
sb.AppendLine($"{i}{call}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits direct object write — bypasses GetWrapper + WriteObject entirely.
|
||||
#region Scan Pass Code Generation
|
||||
|
|
@ -1203,112 +1223,29 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i)
|
||||
{
|
||||
var writer = p.WriterClassName;
|
||||
var nextDepth = "depth + 1";
|
||||
var refSuffix = p.IsIId ? "IId" : "All";
|
||||
|
||||
// Reference type properties can always be null at runtime regardless of nullable annotation
|
||||
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
|
||||
|
||||
// MaxDepth check — matches WriteObjectGenerated
|
||||
sb.AppendLine($"{i} if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
||||
if (!p.ChildNeedsRefScan)
|
||||
if (!p.ChildNeedsRefScan && !p.ChildEnableMetadata)
|
||||
{
|
||||
// Compile-time proven: scan never tracks child → TryConsumeWritePlanEntry always false
|
||||
if (!p.ChildEnableMetadata)
|
||||
{
|
||||
// No ref, no metadata → ZERO branches: always Object
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ref, but metadata possible → UseMetadata branch only
|
||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({p.TypeNameForTypeof}), {writer}.s_wrapperSlot));");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
}
|
||||
// Compile-time proven: no ref, no metadata → ZERO branches: always Object + WriteProperties
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({a}, context, depth + 1); }}");
|
||||
}
|
||||
else if (!p.ChildEnableMetadata)
|
||||
else if (p.ChildNeedsRefScan && !p.ChildEnableMetadata)
|
||||
{
|
||||
// Ref tracking possible, no metadata — Object or ObjectRefFirst/ObjectRef
|
||||
var refGuard = p.IsIId
|
||||
? "context.HasRefHandling"
|
||||
: "context.HasAllRefHandling";
|
||||
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context, depth + 1);");
|
||||
}
|
||||
else if (!p.ChildNeedsRefScan && p.ChildEnableMetadata)
|
||||
{
|
||||
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({a}, context, depth + 1); }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full path: ref tracking + metadata
|
||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({p.TypeNameForTypeof}), {writer}.s_wrapperSlot));");
|
||||
var refGuard = p.IsIId
|
||||
? "context.HasRefHandling"
|
||||
: "context.HasAllRefHandling";
|
||||
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context, depth + 1);");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1386,98 +1323,26 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||
sb.AppendLine($"{i} if (depth + 1 > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||
|
||||
if (!p.ElementNeedsRefScan)
|
||||
var elemRefSuffix = p.ElementIsIId ? "IId" : "All";
|
||||
|
||||
if (!p.ElementNeedsRefScan && !p.ElementEnableMetadata)
|
||||
{
|
||||
// Compile-time proven: scan never tracks element → TryConsumeWritePlanEntry always false
|
||||
if (!p.ElementEnableMetadata)
|
||||
{
|
||||
// No ref, no metadata → ZERO branches per element: always Object
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ref, but metadata possible → UseMetadata branch only
|
||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({p.ElementFullTypeName}), {writer}.s_wrapperSlot));");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
// Compile-time proven: no ref, no metadata → ZERO branches per element: always Object
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
else if (p.ElementNeedsRefScan && !p.ElementEnableMetadata)
|
||||
{
|
||||
sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
else if (!p.ElementNeedsRefScan && p.ElementEnableMetadata)
|
||||
{
|
||||
sb.AppendLine($"{i} context.WriteObjectMetaMarker({e}, {writer}.s_wrapperSlot);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inline ref tracking
|
||||
var elemRefGuard = p.ElementIsIId
|
||||
? "context.HasRefHandling"
|
||||
: "context.HasAllRefHandling";
|
||||
|
||||
if (!p.ElementEnableMetadata)
|
||||
{
|
||||
// Ref tracking possible, no metadata — Object or ObjectRefFirst/ObjectRef
|
||||
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full path: ref tracking + metadata
|
||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({p.ElementFullTypeName}), {writer}.s_wrapperSlot));");
|
||||
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
|
|
@ -1632,111 +1497,34 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
/// <summary>
|
||||
/// Emits inline write for a Complex+SGen dictionary value with ref tracking and metadata support.
|
||||
/// Mirrors EmitDirectCollectionWrite per-element write pattern.
|
||||
/// Delegates marker logic to runtime WriteObjectRefMarker/MetaMarker/FullMarker bridge.
|
||||
/// </summary>
|
||||
private static void EmitDictValueComplexWrite(StringBuilder sb, PropInfo p, string v, string s, string i)
|
||||
{
|
||||
var writer = p.DictValueWriterClassName!;
|
||||
var valType = p.DictValueTypeName!;
|
||||
|
||||
sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}");
|
||||
sb.AppendLine($"{i}else if (nd_{s} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); }}");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
|
||||
if (!p.DictValueNeedsRefScan)
|
||||
var dvRefSuffix = p.DictValueIsIId ? "IId" : "All";
|
||||
|
||||
if (!p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
|
||||
{
|
||||
if (!p.DictValueEnableMetadata)
|
||||
{
|
||||
// No ref, no metadata → always Object
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ref, metadata possible
|
||||
sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({valType}), {writer}.s_wrapperSlot));");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
// No ref, no metadata → always Object
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}");
|
||||
}
|
||||
else if (p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
|
||||
{
|
||||
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
else if (!p.DictValueNeedsRefScan && p.DictValueEnableMetadata)
|
||||
{
|
||||
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var dvRefGuard = p.DictValueIsIId
|
||||
? "context.HasRefHandling"
|
||||
: "context.HasAllRefHandling";
|
||||
|
||||
if (!p.DictValueEnableMetadata)
|
||||
{
|
||||
// Ref tracking, no metadata
|
||||
sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full path: ref tracking + metadata
|
||||
sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapper(typeof({valType}), {writer}.s_wrapperSlot));");
|
||||
sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i)
|
||||
|
|
@ -2079,11 +1867,14 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
{
|
||||
// Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
|
||||
// Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
|
||||
// FixObj slot bytes (0..SlotCount-1) are also valid markers here — populate slot cache
|
||||
// to keep _nextRuntimeSlot in sync with the serializer's _nextTypeSlot counter.
|
||||
if (p.IsNullable)
|
||||
{
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
|
||||
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
|
||||
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
|
||||
sb.AppendLine($"{i} {a} = rc_{p.Name};");
|
||||
|
|
@ -2091,8 +1882,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
else
|
||||
{
|
||||
// ZERO branches — tc is always Object
|
||||
// ZERO branches — tc is always Object or FixObj
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
|
||||
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
|
||||
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
|
||||
sb.AppendLine($"{i} {a} = rc_{p.Name};");
|
||||
|
|
@ -2101,7 +1893,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
else
|
||||
{
|
||||
// Ref tracking possible — Object/ObjectRefFirst/ObjectRef dispatch
|
||||
// Ref tracking possible — Object/ObjectRefFirst/ObjectRef/FixObj dispatch
|
||||
// Inline: parent creates instance + handles cache registration
|
||||
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Object)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
|
|
@ -2121,6 +1913,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
|
||||
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.ObjectRef)");
|
||||
sb.AppendLine($"{i} {a} = {cast}context.GetInternedObject((int)context.ReadVarUInt())!;");
|
||||
// FixObj slot (0..SlotCount-1): same type via FixObj marker (non-meta, non-ref mode)
|
||||
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
|
||||
sb.AppendLine($"{i}else if ({tc} < BinaryTypeCode.Object)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc});");
|
||||
sb.AppendLine($"{i} if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1;");
|
||||
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
|
||||
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
|
||||
sb.AppendLine($"{i} {a} = rc_{p.Name};");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2434,10 +2236,12 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
if (!needsRefScan)
|
||||
{
|
||||
// No ref tracking → only Object or Null in stream — inline ReadProperties
|
||||
// No ref tracking → only Object, FixObj or Null in stream — inline ReadProperties
|
||||
// FixObj slot: populate slot cache to keep _nextRuntimeSlot in sync.
|
||||
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} if ({etc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({elemTypeName}), {etc}); if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1; }}");
|
||||
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
|
||||
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
|
||||
sb.AppendLine($"{i} {assignExpr}");
|
||||
|
|
@ -2445,7 +2249,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
else
|
||||
{
|
||||
// Object hot path first, then ref markers — inline ReadProperties
|
||||
// Object hot path first, then ref markers, then FixObj — inline ReadProperties
|
||||
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Object)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
|
||||
|
|
@ -2466,6 +2270,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} col_{propSuffix}[{indexVar}] = {elemCast}context.GetInternedObject((int)context.ReadVarUInt())!;");
|
||||
else
|
||||
sb.AppendLine($"{i} col_{propSuffix}.{addCall}({elemCast}context.GetInternedObject((int)context.ReadVarUInt())!);");
|
||||
// FixObj slot (0..SlotCount-1): same type via FixObj marker
|
||||
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
|
||||
sb.AppendLine($"{i}else if ({etc} < BinaryTypeCode.Object)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} context.GetWrapper(typeof({elemTypeName}), {etc});");
|
||||
sb.AppendLine($"{i} if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1;");
|
||||
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
|
||||
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
|
||||
sb.AppendLine($"{i} {assignExpr}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -250,59 +250,59 @@ public class AcBinarySerializerDiagnosticTests
|
|||
|
||||
// Log the binary structure
|
||||
Console.WriteLine($"Binary length: {binary.Length}");
|
||||
Console.WriteLine($"Binary hex: {string.Join(" ", binary.Select(b => b.ToString("X2")))}");
|
||||
|
||||
// Parse the header manually to understand structure
|
||||
// === HEADER PARSING (using BinaryTypeCode constants) ===
|
||||
var pos = 0;
|
||||
var version = binary[pos++];
|
||||
Console.WriteLine($"Version: {version}");
|
||||
|
||||
var marker = binary[pos++];
|
||||
Console.WriteLine($"Marker: 0x{marker:X2}");
|
||||
var headerFlags = binary[pos++];
|
||||
Console.WriteLine($"Header flags: 0x{headerFlags:X2}");
|
||||
|
||||
// Skip any header data (strings interning, etc.)
|
||||
// New format uses PropertyIndex directly - no metadata header with property names
|
||||
bool hasMetadata = (headerFlags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
|
||||
bool hasRefOnlyId = (headerFlags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
|
||||
bool hasRefAll = (headerFlags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
|
||||
bool hasCacheCount = (headerFlags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
|
||||
Console.WriteLine($" Metadata={hasMetadata}, RefOnlyId={hasRefOnlyId}, RefAll={hasRefAll}, HasCacheCount={hasCacheCount}");
|
||||
|
||||
// Find Object marker (0x19) or ObjectWithMetadata marker (0x1F)
|
||||
while (pos < binary.Length && binary[pos] != 0x19 && binary[pos] != 0x1F)
|
||||
if (hasCacheCount)
|
||||
{
|
||||
pos++;
|
||||
var ccByte = binary[pos];
|
||||
int cacheCount = (ccByte & 0x80) == 0 ? ccByte : (ccByte & 0x7F) | (binary[pos + 1] << 7);
|
||||
pos += (ccByte & 0x80) == 0 ? 1 : 2;
|
||||
Console.WriteLine($"Cache count: {cacheCount}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"\n=== BODY (starts at position {pos}) ===");
|
||||
|
||||
// The body should start with Object (0x19) or ObjectWithMetadata (0x1F) marker
|
||||
var bodyStart = pos;
|
||||
// Read the object marker — can be FixObj slot (0..SlotCount-1) or explicit marker
|
||||
var objectMarker = binary[pos++];
|
||||
Console.WriteLine($"Object marker: 0x{objectMarker:X2} (0x19=Object, 0x1F=ObjectWithMetadata)");
|
||||
Assert.IsTrue(objectMarker == 0x19 || objectMarker == 0x1F,
|
||||
$"Object marker should be 0x19 or 0x1F, got 0x{objectMarker:X2}");
|
||||
bool isFixObj = objectMarker < BinaryTypeCode.SlotCount;
|
||||
Console.WriteLine($"Object marker: 0x{objectMarker:X2} (FixObj={isFixObj}, " +
|
||||
$"Object=0x{BinaryTypeCode.Object:X2}, ObjectRefFirst=0x{BinaryTypeCode.ObjectRefFirst:X2}, " +
|
||||
$"ObjectWithMetadata=0x{BinaryTypeCode.ObjectWithMetadata:X2})");
|
||||
|
||||
// If ObjectWithMetadata (0x1F), skip inline metadata
|
||||
if (objectMarker == 0x1F)
|
||||
Assert.IsTrue(
|
||||
isFixObj
|
||||
|| objectMarker == BinaryTypeCode.Object
|
||||
|| objectMarker == BinaryTypeCode.ObjectWithMetadata
|
||||
|| objectMarker == BinaryTypeCode.ObjectRefFirst
|
||||
|| objectMarker == BinaryTypeCode.ObjectWithMetadataRefFirst,
|
||||
$"Expected an object marker, got 0x{objectMarker:X2}");
|
||||
|
||||
// If ObjectWithMetadata, skip inline metadata
|
||||
if (objectMarker is BinaryTypeCode.ObjectWithMetadata or BinaryTypeCode.ObjectWithMetadataRefFirst)
|
||||
{
|
||||
// propNameHash (4 bytes)
|
||||
var propNameHash = BitConverter.ToInt32(binary, pos);
|
||||
pos += 4;
|
||||
Console.WriteLine($"PropNameHash: 0x{propNameHash:X8}");
|
||||
|
||||
// First occurrence: propCount (VarUInt) + property hashes
|
||||
// VarUInt: if top bit is set, continue reading
|
||||
var propCountByte = binary[pos];
|
||||
int inlinePropCount;
|
||||
if ((propCountByte & 0x80) == 0)
|
||||
{
|
||||
inlinePropCount = propCountByte;
|
||||
pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi-byte VarUInt - simplified 2-byte parsing
|
||||
inlinePropCount = (propCountByte & 0x7F) | (binary[pos + 1] << 7);
|
||||
pos += 2;
|
||||
}
|
||||
var pcByte = binary[pos];
|
||||
int inlinePropCount = (pcByte & 0x80) == 0 ? pcByte : (pcByte & 0x7F) | (binary[pos + 1] << 7);
|
||||
pos += (pcByte & 0x80) == 0 ? 1 : 2;
|
||||
Console.WriteLine($"Inline metadata propCount: {inlinePropCount}");
|
||||
|
||||
// Skip property hashes (4 bytes each)
|
||||
for (int h = 0; h < inlinePropCount; h++)
|
||||
{
|
||||
var hash = BitConverter.ToInt32(binary, pos);
|
||||
|
|
@ -311,68 +311,57 @@ public class AcBinarySerializerDiagnosticTests
|
|||
}
|
||||
}
|
||||
|
||||
// Read ref ID (if reference handling is enabled)
|
||||
// VarInt: if top bit is set, continue reading
|
||||
var refIdByte = binary[pos];
|
||||
int refId;
|
||||
if ((refIdByte & 0x80) == 0)
|
||||
// If RefFirst marker, read VarUInt cache index
|
||||
if (objectMarker is BinaryTypeCode.ObjectRefFirst or BinaryTypeCode.ObjectWithMetadataRefFirst)
|
||||
{
|
||||
refId = refIdByte;
|
||||
pos++;
|
||||
var rByte = binary[pos];
|
||||
int refCacheIndex = (rByte & 0x80) == 0 ? rByte : (rByte & 0x7F) | (binary[pos + 1] << 7);
|
||||
pos += (rByte & 0x80) == 0 ? 1 : 2;
|
||||
Console.WriteLine($"RefCacheIndex: {refCacheIndex}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi-byte VarInt - simplified parsing
|
||||
refId = -1;
|
||||
pos += 2; // Skip for now
|
||||
}
|
||||
Console.WriteLine($"RefId: {refId}");
|
||||
|
||||
// Read property count in body
|
||||
var bodyPropCount = binary[pos++];
|
||||
Console.WriteLine($"Property count in body: {bodyPropCount}");
|
||||
|
||||
Console.WriteLine($"\n=== BODY PROPERTIES ===");
|
||||
for (int i = 0; i < bodyPropCount && pos < binary.Length; i++)
|
||||
// Markerless format: properties are written in order, no property count header
|
||||
Console.WriteLine($"\n=== BODY PROPERTIES (remaining {binary.Length - pos} bytes) ===");
|
||||
int propIdx = 0;
|
||||
while (pos < binary.Length)
|
||||
{
|
||||
// Log the value (no PropertyIndex in inline metadata mode — properties are in hash order)
|
||||
var valueType = binary[pos];
|
||||
if (valueType == 0x14) // DateTime
|
||||
var b = binary[pos];
|
||||
if (b == BinaryTypeCode.DateTime)
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: DateTime (9 bytes)");
|
||||
pos += 10; // type + 9 bytes
|
||||
Console.WriteLine($" Property [{propIdx}]: DateTime (1+8 bytes)");
|
||||
pos += 9; // marker + 8 bytes ticks
|
||||
}
|
||||
else if (valueType >= 0xC0 && valueType <= 0xFF) // TinyInt (192-255)
|
||||
else if (BinaryTypeCode.IsTinyInt(b))
|
||||
{
|
||||
var tinyValue = valueType - 192 - 16;
|
||||
Console.WriteLine($" Property [{i}]: TinyInt value: {tinyValue}");
|
||||
Console.WriteLine($" Property [{propIdx}]: TinyInt value={BinaryTypeCode.DecodeTinyInt(b)} (0x{b:X2})");
|
||||
pos += 1;
|
||||
}
|
||||
else if (valueType == 0x02) // False (BinaryTypeCode.False = 2)
|
||||
else if (b == BinaryTypeCode.False)
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: Boolean: false");
|
||||
Console.WriteLine($" Property [{propIdx}]: Boolean: false");
|
||||
pos += 1;
|
||||
}
|
||||
else if (valueType == 0x01) // True (BinaryTypeCode.True = 1)
|
||||
else if (b == BinaryTypeCode.True)
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: Boolean: true");
|
||||
Console.WriteLine($" Property [{propIdx}]: Boolean: true");
|
||||
pos += 1;
|
||||
}
|
||||
else if (valueType == 0x00) // Null
|
||||
else if (b == BinaryTypeCode.Null)
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: Null");
|
||||
Console.WriteLine($" Property [{propIdx}]: Null");
|
||||
pos += 1;
|
||||
}
|
||||
else if (valueType == 0xBF) // PropertySkip
|
||||
else if (b == BinaryTypeCode.PropertySkip)
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: PropertySkip (default/null)");
|
||||
Console.WriteLine($" Property [{propIdx}]: PropertySkip (default/null)");
|
||||
pos += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" Property [{i}]: Unknown type: 0x{valueType:X2}");
|
||||
Console.WriteLine($" Property [{propIdx}]: Unknown type: 0x{b:X2}");
|
||||
break;
|
||||
}
|
||||
propIdx++;
|
||||
}
|
||||
|
||||
// Deserialize and verify
|
||||
|
|
|
|||
|
|
@ -22,13 +22,11 @@ namespace AyCode.Core.Tests.Serialization;
|
|||
[TestClass]
|
||||
public class AcBinarySerializerIIdReferenceTests
|
||||
{
|
||||
// BinaryTypeCode.ObjectRef = 27
|
||||
private const byte ObjectRefTypeCode = 27;
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Counts occurrences of ObjectRef (0x1B = 27) in binary data.
|
||||
/// Counts occurrences of ObjectRef in binary data.
|
||||
/// Uses BinaryTypeCode.ObjectRef constant to stay in sync with format changes.
|
||||
/// </summary>
|
||||
private static int CountObjectRefs(byte[] binary, bool writeBinaryToConsole = true)
|
||||
{
|
||||
|
|
@ -37,7 +35,7 @@ public class AcBinarySerializerIIdReferenceTests
|
|||
var count = 0;
|
||||
for (var i = 0; i < binary.Length; i++)
|
||||
{
|
||||
if (binary[i] == ObjectRefTypeCode)
|
||||
if (binary[i] == BinaryTypeCode.ObjectRef)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
|
|
@ -151,7 +149,10 @@ public class AcBinarySerializerIIdReferenceTests
|
|||
{
|
||||
case ReferenceHandlingMode.None:
|
||||
//none esetén miért nincs infinite loop??? - J.
|
||||
Assert.AreEqual(0, objectRefCount, $"[{mode}] Should have 0 ObjectRefs");
|
||||
// Note: CountObjectRefs raw byte scan is unreliable in None mode —
|
||||
// byte 65 (ObjectRef) == ASCII 'A', so "Product-A" and circular-ref
|
||||
// depth expansion produce many false positives. Skip count assertion;
|
||||
// data integrity checks below verify correct deserialization.
|
||||
//WriteBinaryToConsole(binary);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using AyCode.Core.Interfaces;
|
||||
using AyCode.Core.Serializers.Attributes;
|
||||
using AyCode.Core.Serializers.Binaries;
|
||||
|
||||
namespace AyCode.Core.Tests.TestModels;
|
||||
|
|
@ -8,6 +9,7 @@ namespace AyCode.Core.Tests.TestModels;
|
|||
/// </summary>
|
||||
public static class AcSerializerModels
|
||||
{
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestSimpleClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="AyCode.Core.Serializers.Console" />
|
||||
<InternalsVisibleTo Include="AyCode.Core.Serializers.Console" />
|
||||
<InternalsVisibleTo Include="AyCode.Core.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -359,7 +359,16 @@ public static partial class AcBinaryDeserializer
|
|||
context.ReadHeader();
|
||||
var typeCode = context.PeekByte();
|
||||
|
||||
if (typeCode == BinaryTypeCode.Object)
|
||||
if (typeCode < BinaryTypeCode.SlotCount)
|
||||
{
|
||||
// FixObj slot: marker byte is the slot index
|
||||
context.ReadByte();
|
||||
context.GetWrapper(targetType, typeCode);
|
||||
if (typeCode >= context._nextRuntimeSlot)
|
||||
context._nextRuntimeSlot = typeCode + 1;
|
||||
PopulateObject(context, target, targetType, 0);
|
||||
}
|
||||
else if (typeCode == BinaryTypeCode.Object)
|
||||
{
|
||||
context.ReadByte();
|
||||
PopulateObject(context, target, targetType, 0);
|
||||
|
|
@ -520,7 +529,16 @@ public static partial class AcBinaryDeserializer
|
|||
context.ReadHeader();
|
||||
var typeCode = context.PeekByte();
|
||||
|
||||
if (typeCode == BinaryTypeCode.Object)
|
||||
if (typeCode < BinaryTypeCode.SlotCount)
|
||||
{
|
||||
// FixObj slot: marker byte is the slot index
|
||||
context.ReadByte();
|
||||
context.GetWrapper(targetType, typeCode);
|
||||
if (typeCode >= context._nextRuntimeSlot)
|
||||
context._nextRuntimeSlot = typeCode + 1;
|
||||
PopulateObject(context, target, targetType, 0);
|
||||
}
|
||||
else if (typeCode == BinaryTypeCode.Object)
|
||||
{
|
||||
context.ReadByte();
|
||||
PopulateObject(context, target, targetType, 0);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,457 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AyCode.Core.Serializers.Binaries;
|
||||
|
||||
public static partial class AcBinarySerializer
|
||||
{
|
||||
internal sealed partial class BinarySerializationContext<TOutput>
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
#region Property Writer Bridges — used by SGen generated code
|
||||
|
||||
/// <summary>
|
||||
/// Writes an Int32 property value. UseMetadata: skip if 0, TinyInt or Int32+VarInt. Markerless: VarInt only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteInt32Property(int value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny)) { WriteByte(tiny); return; }
|
||||
WriteByte(BinaryTypeCode.Int32);
|
||||
}
|
||||
|
||||
WriteVarInt(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an Int64 property value. UseMetadata: skip if 0, int-range TinyInt or Int64+VarLong. Markerless: VarLong only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteInt64Property(long value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0L) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
if (value >= int.MinValue && value <= int.MaxValue)
|
||||
{
|
||||
var iv = (int)value;
|
||||
if (BinaryTypeCode.TryEncodeTinyInt(iv, out var tiny)) { WriteByte(tiny); return; }
|
||||
WriteByte(BinaryTypeCode.Int32);
|
||||
WriteVarInt(iv);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.Int64);
|
||||
WriteVarLong(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVarLong(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Boolean property value. UseMetadata: skip if false, else True. Markerless: 1/0 byte.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteBoolProperty(bool value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
WriteByte(value ? BinaryTypeCode.True : BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteByte(value ? (byte)1 : (byte)0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Double property value. UseMetadata: skip if 0.0, else Float64+Raw. Markerless: Raw only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteFloat64Property(double value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0.0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteTypeCodeAndRaw(BinaryTypeCode.Float64, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Single property value. UseMetadata: skip if 0f, else Float32+Raw. Markerless: Raw only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteFloat32Property(float value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0f) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteTypeCodeAndRaw(BinaryTypeCode.Float32, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Decimal property value. UseMetadata: skip if 0m, else Decimal+Bits. Markerless: DecimalBits only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteDecimalProperty(decimal value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0m) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.Decimal);
|
||||
}
|
||||
|
||||
WriteDecimalBits(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Guid property value. UseMetadata: skip if Empty, else Guid+Bits. Markerless: GuidBits only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteGuidProperty(Guid value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == Guid.Empty) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.Guid);
|
||||
}
|
||||
|
||||
WriteGuidBits(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a DateTime property value. UseMetadata: DateTime+Bits (no skip). Markerless: DateTimeBits only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteDateTimeProperty(DateTime value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.DateTime);
|
||||
}
|
||||
|
||||
WriteDateTimeBits(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a Byte property value. UseMetadata: skip if 0, else UInt8+byte. Markerless: byte only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteByteProperty(byte value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.UInt8);
|
||||
}
|
||||
|
||||
WriteByte(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an Int16 property value. UseMetadata: skip if 0, else Int16+Raw. Markerless: Raw only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteInt16Property(short value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteTypeCodeAndRaw(BinaryTypeCode.Int16, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UInt16 property value. UseMetadata: skip if 0, else UInt16+Raw. Markerless: Raw only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteUInt16Property(ushort value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteRaw(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UInt32 property value. UseMetadata: skip if 0, else UInt32+VarUInt. Markerless: VarUInt only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteUInt32Property(uint value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.UInt32);
|
||||
}
|
||||
|
||||
WriteVarUInt(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a UInt64 property value. UseMetadata: skip if 0, else UInt64+VarULong. Markerless: VarULong only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteUInt64Property(ulong value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.UInt64);
|
||||
}
|
||||
|
||||
WriteVarULong(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an Enum property value (pre-cast to int). UseMetadata: skip if 0, else Enum+TinyInt/VarInt. Markerless: VarInt only.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteEnumInt32Property(int value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
|
||||
WriteByte(BinaryTypeCode.Enum);
|
||||
if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny))
|
||||
WriteByte(tiny);
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.Int32);
|
||||
WriteVarInt(value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteVarInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a TimeSpan property value. UseMetadata: TimeSpan+Raw(Ticks). Markerless: Raw(Ticks) only. No skip.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteTimeSpanProperty(TimeSpan value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, value.Ticks);
|
||||
else
|
||||
WriteRaw(value.Ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a DateTimeOffset property value. UseMetadata: DateTimeOffset+Bits. Markerless: DateTimeOffsetBits only. No skip.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteDateTimeOffsetProperty(DateTimeOffset value)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.DateTimeOffset);
|
||||
}
|
||||
|
||||
WriteDateTimeOffsetBits(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Marker — SGen bridge for complex child writes
|
||||
// SGen selects the correct variant at compile-time based on ChildNeedsRefScan / ChildEnableMetadata / IsIId.
|
||||
// The 4th case (!ref && !meta) is handled inline by SGen: Object + WriteProperties (ZERO branches).
|
||||
// Marker methods write only the object marker bytes. WriteProperties is called directly in SGen
|
||||
// (preserving direct call — no interface dispatch on IGeneratedBinaryWriter).
|
||||
|
||||
/// <summary>
|
||||
/// Ref tracking (IId) only, no metadata. Uses HasRefHandling.
|
||||
/// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool WriteObjectRefMarkerIId()
|
||||
{
|
||||
if (HasRefHandling && TryConsumeWritePlanEntry(out var pe))
|
||||
{
|
||||
if (!pe.IsFirst)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRef);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteByte(BinaryTypeCode.ObjectRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
WriteByte(BinaryTypeCode.Object);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ref tracking (AllRef) only, no metadata. Uses HasAllRefHandling.
|
||||
/// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal bool WriteObjectRefMarkerAll()
|
||||
{
|
||||
if (HasAllRefHandling && TryConsumeWritePlanEntry(out var pe))
|
||||
{
|
||||
if (!pe.IsFirst)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRef);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteByte(BinaryTypeCode.ObjectRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
WriteByte(BinaryTypeCode.Object);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata only, no ref tracking. Writes ObjectWithMetadata or Object marker.
|
||||
/// Always returns — caller always calls WriteProperties after this.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void WriteObjectMetaMarker(object value, int wrapperSlot)
|
||||
{
|
||||
if (UseMetadata)
|
||||
{
|
||||
var wrapper = GetWrapper(value.GetType(), wrapperSlot);
|
||||
var isFirstMeta = RegisterMetadataType(wrapper);
|
||||
WriteByte(BinaryTypeCode.ObjectWithMetadata);
|
||||
WriteInlineMetadata(wrapper.Metadata, isFirstMeta);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.Object);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full path (IId): ref tracking + metadata. Uses HasRefHandling.
|
||||
/// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
|
||||
/// </summary>
|
||||
internal bool WriteObjectFullMarkerIId(object value, int wrapperSlot)
|
||||
{
|
||||
var useMetadata = UseMetadata;
|
||||
bool isFirstMeta = false;
|
||||
if (useMetadata)
|
||||
{
|
||||
var wrapper = GetWrapper(value.GetType(), wrapperSlot);
|
||||
isFirstMeta = RegisterMetadataType(wrapper);
|
||||
}
|
||||
|
||||
if (HasRefHandling && TryConsumeWritePlanEntry(out var pe))
|
||||
{
|
||||
if (!pe.IsFirst)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRef);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
WriteInlineMetadata(GetWrapper(value.GetType(), wrapperSlot).Metadata, isFirstMeta);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (useMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectWithMetadata);
|
||||
WriteInlineMetadata(GetWrapper(value.GetType(), wrapperSlot).Metadata, isFirstMeta);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.Object);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full path (AllRef): ref tracking + metadata. Uses HasAllRefHandling.
|
||||
/// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
|
||||
/// </summary>
|
||||
internal bool WriteObjectFullMarkerAll(object value, int wrapperSlot)
|
||||
{
|
||||
var useMetadata = UseMetadata;
|
||||
bool isFirstMeta = false;
|
||||
if (useMetadata)
|
||||
{
|
||||
var wrapper = GetWrapper(value.GetType(), wrapperSlot);
|
||||
isFirstMeta = RegisterMetadataType(wrapper);
|
||||
}
|
||||
|
||||
if (HasAllRefHandling && TryConsumeWritePlanEntry(out var pe))
|
||||
{
|
||||
if (!pe.IsFirst)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRef);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (useMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
WriteInlineMetadata(GetWrapper(value.GetType(), wrapperSlot).Metadata, isFirstMeta);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectRefFirst);
|
||||
WriteVarUInt((uint)pe.CacheMapIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (useMetadata)
|
||||
{
|
||||
WriteByte(BinaryTypeCode.ObjectWithMetadata);
|
||||
WriteInlineMetadata(GetWrapper(value.GetType(), wrapperSlot).Metadata, isFirstMeta);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteByte(BinaryTypeCode.Object);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ public static partial class AcBinarySerializer
|
|||
/// All write operations (WriteByte, WriteVarUInt, etc.) are inline methods here.
|
||||
/// TOutput Output handles only cold-path buffer management (Grow/Initialize) and finalization.
|
||||
/// </summary>
|
||||
internal sealed class BinarySerializationContext<TOutput>
|
||||
internal sealed partial class BinarySerializationContext<TOutput>
|
||||
: SerializationContextBase<BinarySerializeTypeMetadata, AcBinarySerializerOptions>, IDisposable
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1327,11 +1327,7 @@ public static partial class AcBinarySerializer
|
|||
{
|
||||
var prop = properties[i];
|
||||
|
||||
if (prop.ExpectedTypeCode.HasValue)
|
||||
{
|
||||
WritePropertyMarkerless(value, prop, context);
|
||||
}
|
||||
else if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
if (!prop.ExpectedTypeCode.HasValue && hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
}
|
||||
|
|
@ -1537,8 +1533,9 @@ public static partial class AcBinarySerializer
|
|||
|
||||
/// <summary>
|
||||
/// Writes a property value OR a skip marker if the value is default/null.
|
||||
/// Single-pass optimization: checks default + writes value in one operation.
|
||||
/// Avoids double getter calls.
|
||||
/// Delegates to PropertyWriter bridge methods which handle UseMetadata internally:
|
||||
/// UseMetadata=true: skip marker for defaults, type code + value for non-defaults.
|
||||
/// UseMetadata=false (markerless): raw value only, no skip markers.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WritePropertyOrSkip<TOutput>(object obj, BinaryPropertyAccessor prop, TypeMetadataWrapper<BinarySerializeTypeMetadata> parentWrapper, BinarySerializationContext<TOutput> context, int depth)
|
||||
|
|
@ -1547,143 +1544,47 @@ public static partial class AcBinarySerializer
|
|||
switch (prop.AccessorType)
|
||||
{
|
||||
case PropertyAccessorType.Int32:
|
||||
{
|
||||
int value = prop.GetInt32(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteInt32(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteInt32Property(prop.GetInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int64:
|
||||
{
|
||||
long value = prop.GetInt64(obj);
|
||||
if (value == 0L)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteInt64(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteInt64Property(prop.GetInt64(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Boolean:
|
||||
{
|
||||
bool value = prop.GetBoolean(obj);
|
||||
if (!value)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
context.WriteByte(BinaryTypeCode.True);
|
||||
return;
|
||||
}
|
||||
context.WriteBoolProperty(prop.GetBoolean(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Double:
|
||||
{
|
||||
double value = prop.GetDouble(obj);
|
||||
if (value == 0.0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteFloat64Unsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteFloat64Property(prop.GetDouble(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Single:
|
||||
{
|
||||
float value = prop.GetSingle(obj);
|
||||
if (value == 0f)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteFloat32Unsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteFloat32Property(prop.GetSingle(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Decimal:
|
||||
{
|
||||
decimal value = prop.GetDecimal(obj);
|
||||
if (value == 0m)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteDecimalUnsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteDecimalProperty(prop.GetDecimal(obj));
|
||||
return;
|
||||
case PropertyAccessorType.DateTime:
|
||||
{
|
||||
DateTime value = prop.GetDateTime(obj);
|
||||
// DateTime always written (no default skip)
|
||||
WriteDateTimeUnsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteDateTimeProperty(prop.GetDateTime(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Byte:
|
||||
{
|
||||
byte value = prop.GetByte(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.UInt8);
|
||||
context.WriteByte(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
context.WriteByteProperty(prop.GetByte(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int16:
|
||||
{
|
||||
short value = prop.GetInt16(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteInt16Unsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteInt16Property(prop.GetInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt16:
|
||||
{
|
||||
ushort value = prop.GetUInt16(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteUInt16Unsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteUInt16Property(prop.GetUInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt32:
|
||||
{
|
||||
uint value = prop.GetUInt32(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteUInt32(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteUInt32Property(prop.GetUInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt64:
|
||||
{
|
||||
ulong value = prop.GetUInt64(obj);
|
||||
if (value == 0)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteUInt64(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteUInt64Property(prop.GetUInt64(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Guid:
|
||||
{
|
||||
Guid value = prop.GetGuid(obj);
|
||||
if (value == Guid.Empty)
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
else
|
||||
WriteGuidUnsafe(value, context);
|
||||
return;
|
||||
}
|
||||
context.WriteGuidProperty(prop.GetGuid(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Enum:
|
||||
{
|
||||
int enumValue = prop.GetEnumAsInt32(obj);
|
||||
if (enumValue == 0)
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
}
|
||||
else if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.Enum);
|
||||
context.WriteByte(tiny);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.Enum);
|
||||
context.WriteByte(BinaryTypeCode.Int32);
|
||||
context.WriteVarInt(enumValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
context.WriteEnumInt32Property(prop.GetEnumAsInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.String:
|
||||
{
|
||||
// Fast path: typed getter, no boxing, no Type.GetTypeCode() call
|
||||
|
|
@ -1744,62 +1645,6 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a property value without type marker byte (markerless mode, UseMetadata=false).
|
||||
/// All values are written including defaults — no PropertySkip markers.
|
||||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WritePropertyMarkerless<TOutput>(object obj, BinaryPropertyAccessor prop, BinarySerializationContext<TOutput> context)
|
||||
where TOutput : struct, IBinaryOutputBase
|
||||
{
|
||||
switch (prop.AccessorType)
|
||||
{
|
||||
case PropertyAccessorType.Int32:
|
||||
context.WriteVarInt(prop.GetInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int64:
|
||||
context.WriteVarLong(prop.GetInt64(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Double:
|
||||
context.WriteRaw(prop.GetDouble(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Single:
|
||||
context.WriteRaw(prop.GetSingle(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Decimal:
|
||||
context.WriteDecimalBits(prop.GetDecimal(obj));
|
||||
return;
|
||||
case PropertyAccessorType.DateTime:
|
||||
context.WriteDateTimeBits(prop.GetDateTime(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Guid:
|
||||
context.WriteGuidBits(prop.GetGuid(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Byte:
|
||||
context.WriteByte(prop.GetByte(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int16:
|
||||
context.WriteRaw(prop.GetInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt16:
|
||||
context.WriteRaw(prop.GetUInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt32:
|
||||
context.WriteVarUInt(prop.GetUInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt64:
|
||||
context.WriteVarULong(prop.GetUInt64(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Boolean:
|
||||
context.WriteByte(prop.GetBoolean(obj) ? (byte)1 : (byte)0);
|
||||
return;
|
||||
case PropertyAccessorType.Enum:
|
||||
context.WriteVarInt(prop.GetEnumAsInt32(obj));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Specialized Array Writers
|
||||
|
|
|
|||
Loading…
Reference in New Issue