Remove depth param from serializers; use context field

Refactored AcBinary, AcJson, and AcToon serializers to eliminate the explicit depth parameter from all serialization/deserialization methods, generated code, and interfaces. Introduced a global RecursionDepth field on the serialization context, incremented/decremented at recursion entry/exit, and enforced against MaxDepth as a safety net (except when ReferenceHandling=All). Updated all usages, including property, array, and dictionary handling, to use the new context-based depth tracking. Ensured consistency across runtime and generated code.
This commit is contained in:
Loretta 2026-05-13 23:02:15 +02:00
parent b849beb2ee
commit ac6e66f59f
17 changed files with 367 additions and 404 deletions

File diff suppressed because one or more lines are too long

View File

@ -542,8 +542,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.Append(string.Join(", ", ci.PropertyNameHashes));
sb.AppendLine(" };");
sb.AppendLine();
sb.AppendLine(" public void WriteProperties<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth) where TOutput : struct, IBinaryOutputBase");
sb.AppendLine(" public void WriteProperties<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase");
sb.AppendLine(" {");
sb.AppendLine(" // Global recursion depth safety net — only when ReferenceHandling != All");
sb.AppendLine(" // (HasAllRefHandling=true tracks every type → write plan already prevents cycles via ObjectRef).");
sb.AppendLine(" var needsDepthCheck = !context.HasAllRefHandling;");
sb.AppendLine(" if (needsDepthCheck)");
sb.AppendLine(" {");
sb.AppendLine(" if (context.RecursionDepth >= context.MaxDepth)");
sb.AppendLine($" throw new System.InvalidOperationException(\"AcBinary serialize: recursion depth exceeded MaxDepth=\" + context.MaxDepth + \" at {ci.FullTypeName}\");");
sb.AppendLine(" context.RecursionDepth++;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
foreach (var p in ci.Properties)
@ -552,6 +562,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
EmitProp(sb, p, " ", ci.FullTypeName, ci.EnableMetadata, ci.EnablePropertyFilter);
}
sb.AppendLine();
sb.AppendLine(" if (needsDepthCheck) context.RecursionDepth--;");
sb.AppendLine(" }");
sb.AppendLine();
@ -565,7 +577,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine(" where TOutput : struct, IBinaryOutputBase");
sb.AppendLine(" {");
sb.AppendLine(" if (!context.HasCaching) return;");
sb.AppendLine(" ScanObject(value, context, 0);");
sb.AppendLine(" ScanObject(value, context);");
sb.AppendLine(" context.SortWritePlan();");
sb.AppendLine(" }");
@ -580,7 +592,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
/// </summary>
private static void GenScanProperties(StringBuilder sb, SerializableClassInfo ci)
{
sb.AppendLine(" public void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth) where TOutput : struct, IBinaryOutputBase");
sb.AppendLine(" public void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase");
sb.AppendLine(" {");
// Compile-time proven: no scan work needed for this type
@ -602,8 +614,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine(" if (!context.HasStringInterning) return;");
}
// Null/depth guard — matches runtime ScanValue entry
sb.AppendLine(" if (value == null || depth > context.MaxDepth) return;");
// Null guard — MaxDepth option removed (was: cycle protection via runtime depth check).
// Cycle safety now comes from IId-tracking; future [AcBinaryCircular] attr will mark non-IId circular refs.
sb.AppendLine(" if (value == null) return;");
sb.AppendLine();
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
@ -663,6 +676,19 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
(p.TypeKind == PropertyTypeKind.Collection && p.ElementKind == PropertyTypeKind.String && p.InterningFlags != 0) ||
(p.TypeKind == PropertyTypeKind.Dictionary && (p.DictKeyKind == PropertyTypeKind.String || p.DictValueKind == PropertyTypeKind.String) && p.InterningFlags != 0));
// Global recursion depth safety net — only when ReferenceHandling != All
// (HasAllRefHandling=true tracks every type → 2nd-occurrence early-return already prevents cycles).
// Emitted AFTER all early returns (NeedsScan=false, feature-flag, null guard, IId 2nd-occurrence)
// and BEFORE the property scan loop that recurses into children.
sb.AppendLine();
sb.AppendLine(" var needsDepthCheck = !context.HasAllRefHandling;");
sb.AppendLine(" if (needsDepthCheck)");
sb.AppendLine(" {");
sb.AppendLine(" if (context.RecursionDepth >= context.MaxDepth)");
sb.AppendLine($" throw new System.InvalidOperationException(\"AcBinary scan: recursion depth exceeded MaxDepth=\" + context.MaxDepth + \" at {ci.FullTypeName}\");");
sb.AppendLine(" context.RecursionDepth++;");
sb.AppendLine(" }");
if (hasStringScan)
{
// Use pre-computed InternBit from context (avoids Options.UseStringInterning field chain + shift per object).
@ -688,6 +714,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine(" // No reference properties to scan");
}
sb.AppendLine();
sb.AppendLine(" if (needsDepthCheck) context.RecursionDepth--;");
sb.AppendLine(" }");
}
@ -774,16 +802,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} context.WriteStringUtf8({a}.GetType().AssemblyQualifiedName!);");
sb.AppendLine($"{i} }}");
}
sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);");
sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context);");
sb.AppendLine($"{i}}}");
}
else if (p.IsNullable)
{
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
}
else
sb.AppendLine($"{i}AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
sb.AppendLine($"{i}AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
break;
case PropertyTypeKind.Collection:
// Direct collection write for List<T>/T[] with Complex element types that have generated writers
@ -792,10 +820,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
else if (p.IsNullable)
{
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
}
else
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context);");
break;
case PropertyTypeKind.Dictionary:
EmitDirectDictionaryWrite(sb, p, a, i);
@ -1024,7 +1052,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var {childVar} = {a};");
sb.AppendLine($"{i} if ({childVar} != null)");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context, depth + 1);");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);");
sb.AppendLine($"{i}}}");
}
else
@ -1032,7 +1060,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// IId in subtree — always call (active in OnlyId + All)
sb.AppendLine($"{i}var {childVar} = {a};");
sb.AppendLine($"{i}if ({childVar} != null)");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context, depth + 1);");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({childVar}, context);");
}
}
@ -1046,9 +1074,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}var {childVar} = {a};");
sb.AppendLine($"{i}if ({childVar} != null)");
if (p.IsObjectDeclaredType)
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context, depth + 1);");
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context);");
else
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);");
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context);");
}
/// <summary>
@ -1130,7 +1158,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var scol_{p.Name} = {a};");
sb.AppendLine($"{i} if (scol_{p.Name} != null)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var snd_{p.Name} = depth + 2;");
if (p.CollectionKind == "Array")
{
@ -1160,7 +1187,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
var e = $"se_{p.Name}";
// Null check only — ScanObject handles depth + ref tracking internally
sb.AppendLine($"{i} if ({e} == null) continue;");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({e}, context, snd_{p.Name});");
sb.AppendLine($"{i} {writer}.Instance.ScanObject({e}, context);");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i}}}");
@ -1172,7 +1199,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i}var scol_{p.Name} = {a};");
sb.AppendLine($"{i}if (scol_{p.Name} != null)");
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated(scol_{p.Name}, typeof({p.TypeNameForTypeof}), context, depth);");
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated(scol_{p.Name}, typeof({p.TypeNameForTypeof}), context);");
return;
}
@ -1253,7 +1280,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var sd_{s} = {a};");
sb.AppendLine($"{i} if (sd_{s} != null)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var snd_{s} = depth + 2;");
sb.AppendLine($"{i} foreach (var sde_{s} in sd_{s})");
sb.AppendLine($"{i} {{");
@ -1287,11 +1313,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} if ({complexGuard})");
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});");
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context);");
sb.AppendLine($"{i} }}");
}
else
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});");
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context);");
}
sb.AppendLine($"{i} }}");
@ -1313,24 +1339,23 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// 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 if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
if (!p.ChildNeedsRefScan && !p.ChildEnableMetadata)
{
// 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); }}");
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({a}, context); }}");
}
else if (p.ChildNeedsRefScan && !p.ChildEnableMetadata)
{
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context, depth + 1);");
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{refSuffix}()) {writer}.Instance.WriteProperties({a}, context);");
}
else if (!p.ChildNeedsRefScan && p.ChildEnableMetadata)
{
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({a}, context, depth + 1); }}");
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({a}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({a}, context); }}");
}
else
{
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context, depth + 1);");
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{refSuffix}({a}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({a}, context);");
}
}
@ -1361,7 +1386,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// Reference type collections can always be null at runtime regardless of nullable annotation
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
@ -1372,8 +1396,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i} var arr_{p.Name} = {a};");
sb.AppendLine($"{i} context.WriteVarUInt((uint)arr_{p.Name}.Length);");
sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;");
sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;");
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < arr_{p.Name}.Length; i_{p.Name}++)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];");
@ -1382,8 +1404,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i} var col_{p.Name} = {a};");
sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);");
sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;");
sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;");
sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})");
sb.AppendLine($"{i} {{");
}
@ -1391,8 +1411,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i} var col_{p.Name} = {a};");
sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);");
sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;");
sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;");
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < col_{p.Name}.Count; i_{p.Name}++)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var elem_{p.Name} = col_{p.Name}[i_{p.Name}];");
@ -1401,8 +1419,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan({a});");
sb.AppendLine($"{i} context.WriteVarUInt((uint)span_{p.Name}.Length);");
sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 2;");
sb.AppendLine($"{i} var depthExceeded_{p.Name} = depth + 1 > context.MaxDepth;");
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < span_{p.Name}.Length; i_{p.Name}++)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var elem_{p.Name} = span_{p.Name}[i_{p.Name}];");
@ -1410,7 +1426,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// Per-element write
var e = $"elem_{p.Name}";
sb.AppendLine($"{i} if ({e} == null || depthExceeded_{p.Name}) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
var elemRefSuffix = p.ElementIsIId ? "IId" : "All";
@ -1418,20 +1434,20 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
{
// 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});");
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context);");
}
else if (p.ElementNeedsRefScan && !p.ElementEnableMetadata)
{
sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
sb.AppendLine($"{i} if (context.WriteObjectRefMarker{elemRefSuffix}()) {writer}.Instance.WriteProperties({e}, context);");
}
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});");
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context);");
}
else
{
sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
sb.AppendLine($"{i} if (context.WriteObjectFullMarker{elemRefSuffix}({e}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({e}, context);");
}
sb.AppendLine($"{i} }}");
@ -1520,13 +1536,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// Reference type dictionaries can always be null at runtime regardless of nullable annotation
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
sb.AppendLine($"{i}else if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Dictionary);");
sb.AppendLine($"{i} context.WriteVarUInt((uint){a}.Count);");
sb.AppendLine($"{i} var nd_{s} = depth + 2;");
sb.AppendLine($"{i} foreach (var kvp_{s} in {a})");
sb.AppendLine($"{i} {{");
@ -1549,7 +1563,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
}
else
{
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context, nd_{s});");
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context);");
}
// Write value
@ -1577,7 +1591,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
else
{
// Fallback for non-inlineable value types
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context, nd_{s});");
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context);");
}
sb.AppendLine($"{i} }}");
@ -1593,26 +1607,25 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
var writer = p.DictValueWriterClassName!;
sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}");
sb.AppendLine($"{i}else if (nd_{s} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); }}");
var dvRefSuffix = p.DictValueIsIId ? "IId" : "All";
if (!p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
{
// No ref, no metadata → always Object
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}");
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Object); {writer}.Instance.WriteProperties({v}, context); }}");
}
else if (p.DictValueNeedsRefScan && !p.DictValueEnableMetadata)
{
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context, nd_{s});");
sb.AppendLine($"{i}else if (context.WriteObjectRefMarker{dvRefSuffix}()) {writer}.Instance.WriteProperties({v}, context);");
}
else if (!p.DictValueNeedsRefScan && p.DictValueEnableMetadata)
{
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({v}, context, nd_{s}); }}");
sb.AppendLine($"{i}else {{ context.WriteObjectMetaMarker({v}, {writer}.s_wrapperSlot); {writer}.Instance.WriteProperties({v}, context); }}");
}
else
{
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context, nd_{s});");
sb.AppendLine($"{i}else if (context.WriteObjectFullMarker{dvRefSuffix}({v}, {writer}.s_wrapperSlot)) {writer}.Instance.WriteProperties({v}, context);");
}
}
@ -1700,7 +1713,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});");
break;
default:
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context, depth);");
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({typeName}), context);");
break;
}
}
@ -1746,7 +1759,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine();
// ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
sb.AppendLine(" public void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth)");
// No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
// already prevents pathological depth in well-formed payloads.
sb.AppendLine(" public void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
@ -1762,13 +1777,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine();
// ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
sb.AppendLine(" public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex)");
sb.AppendLine(" public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int cacheIndex)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = new {ci.FullTypeName}();");
sb.AppendLine(" if (cacheIndex >= 0)");
sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
sb.AppendLine(" ReadProperties<TInput>(obj, context, depth);");
sb.AppendLine(" ReadProperties<TInput>(obj, context);");
sb.AppendLine(" return obj;");
sb.AppendLine(" }");
sb.AppendLine("}");
@ -1856,9 +1871,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
sb.AppendLine($"{i} context._position--;");
if (p.IsNullable)
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
else
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
break;
}
}
@ -2013,20 +2028,19 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
sb.AppendLine($"{i}}}");
}
else
{
sb.AppendLine($"{i}context._position--;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
}
return;
}
var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
var cast = $"({p.TypeNameForTypeof})";
var nd = "depth + 1";
if (!p.ChildNeedsRefScan)
{
@ -2041,7 +2055,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
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} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
@ -2051,7 +2065,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
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} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
@ -2069,7 +2083,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
@ -2078,7 +2092,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var ci_{p.Name} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{p.Name}, rc_{p.Name});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context, {nd});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
@ -2095,7 +2109,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
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} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} break;");
@ -2136,13 +2150,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1);");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
sb.AppendLine($"{i}}}");
}
else
{
sb.AppendLine($"{i}context._position--;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}), depth + 1)!;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
}
}
@ -2176,7 +2190,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var dict_{s} = new System.Collections.Generic.Dictionary<{keyType}, {valType}>(cnt_{s});");
sb.AppendLine($"{i} var nd_{s} = depth + 1;");
sb.AppendLine($"{i} for (var di_{s} = 0; di_{s} < cnt_{s}; di_{s}++)");
sb.AppendLine($"{i} {{");
@ -2184,7 +2197,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
if (canInlineKey)
EmitReadDictElement(sb, p.DictKeyKind, keyType, $"dk_{s}", s, i + " ", null, false);
else
sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}), nd_{s})!;");
sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}))!;");
// Read value
if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter)
@ -2196,7 +2209,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRefFirst)");
@ -2204,7 +2217,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var rci_{s} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(rci_{s}, rv_{s});");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context, nd_{s});");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)");
@ -2212,13 +2225,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});");
sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");
sb.AppendLine($"{i} }}");
}
else if (canInlineValue)
EmitReadDictElement(sb, p.DictValueKind, valType, $"dv_{s}", s, i + " ", null, true);
else
sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}), nd_{s});");
sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");
// Add to dictionary
sb.AppendLine($"{i} if (dk_{s} != null) dict_{s}[dk_{s}] = dv_{s}!;");
@ -2351,8 +2364,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
if (isComplexElement)
sb.AppendLine($"{i} var nd_{s} = depth + 1;");
// Create collection + loop based on kind
if (p.CollectionKind == "Array")
@ -2420,7 +2431,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
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} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i}}}");
}
@ -2434,7 +2445,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
@ -2443,7 +2454,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} var ci_{propSuffix} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{propSuffix}, re_{propSuffix});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context, nd_{propSuffix});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
@ -2464,7 +2475,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
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} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} break;");

View File

@ -21,22 +21,19 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter
{
internal static readonly TestOrderWriter Instance = new();
public void WriteProperties<TOutput>(
object value,
AcBinarySerializer.BinarySerializationContext<TOutput> context,
int depth)
public void WriteProperties<TOutput>(object value,
AcBinarySerializer.BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
var obj = Unsafe.As<TestOrder_All_True>(value);
var nextDepth = depth;
// Properties in alphabetical order (matching runtime serializer):
// AuditMetadata: MetadataInfo_All_True? (complex, nullable)
WriteComplexOrNull(obj.AuditMetadata, context, nextDepth);
WriteComplexOrNull(obj.AuditMetadata, context);
// Category: SharedCategory_All_True? (complex, nullable)
WriteComplexOrNull(obj.Category, context, nextDepth);
WriteComplexOrNull(obj.Category, context);
// CreatedAt: DateTime (markerless)
context.WriteDateTimeBits(obj.CreatedAt);
@ -45,22 +42,22 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter
context.WriteVarInt(obj.Id);
// Items: List<TestOrderItem_All_True> (collection)
WriteComplexOrNull(obj.Items, context, nextDepth);
WriteComplexOrNull(obj.Items, context);
// MetadataList: List<MetadataInfo_All_True> (collection)
WriteComplexOrNull(obj.MetadataList, context, nextDepth);
WriteComplexOrNull(obj.MetadataList, context);
// NoMergeItems: List<TestOrderItem_All_True> (collection)
WriteComplexOrNull(obj.NoMergeItems, context, nextDepth);
WriteComplexOrNull(obj.NoMergeItems, context);
// OrderMetadata: MetadataInfo_All_True? (complex, nullable)
WriteComplexOrNull(obj.OrderMetadata, context, nextDepth);
WriteComplexOrNull(obj.OrderMetadata, context);
// OrderNumber: string
AcBinarySerializer.WriteStringGenerated(obj.OrderNumber, context);
// Owner: SharedUser? (complex, nullable)
WriteComplexOrNull(obj.Owner, context, nextDepth);
WriteComplexOrNull(obj.Owner, context);
// PaidDateUtc: DateTime? (nullable)
var paidDate = obj.PaidDateUtc;
@ -75,22 +72,22 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter
}
// PrimaryTag: SharedTag_All_True? (complex, nullable)
WriteComplexOrNull(obj.PrimaryTag, context, nextDepth);
WriteComplexOrNull(obj.PrimaryTag, context);
// SecondaryTag: SharedTag_All_True? (complex, nullable)
WriteComplexOrNull(obj.SecondaryTag, context, nextDepth);
WriteComplexOrNull(obj.SecondaryTag, context);
// Status: TestStatus (enum, markerless)
context.WriteVarInt((int)obj.Status);
// Tags: List<SharedTag_All_True> (collection)
WriteComplexOrNull(obj.Tags, context, nextDepth);
WriteComplexOrNull(obj.Tags, context);
// TotalAmount: decimal (markerless)
context.WriteDecimalBits(obj.TotalAmount);
}
public void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth) where TOutput : struct, IBinaryOutputBase
public void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase
{
throw new NotImplementedException();
}
@ -98,12 +95,12 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter
public void ScanForDuplicates<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase
{
if (!context.HasCaching) return;
ScanObject(value, context, 0);
ScanObject(value, context);
context.SortWritePlan();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteComplexOrNull<TOutput>(object? value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth)
private static void WriteComplexOrNull<TOutput>(object? value, AcBinarySerializer.BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
if (value == null)
@ -112,6 +109,6 @@ internal sealed class TestOrderWriter : IGeneratedBinaryWriter
return;
}
AcBinarySerializer.WriteValueGenerated(value, value.GetType(), context, depth);
AcBinarySerializer.WriteValueGenerated(value, value.GetType(), context);
}
}

View File

@ -24,6 +24,7 @@ public abstract class AcSerializerContextBase<TMetadata, TOptions>
public TOptions Options { get; private set; } = null!;
public byte MaxDepth => Options.MaxDepth;
public ReferenceHandlingMode ReferenceHandling => Options.ReferenceHandling;
/// <summary>

View File

@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer
BinaryTypeCode.Null => null,
BinaryTypeCode.Object => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: false),
BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: true),
_ => ReadValue(context, destType, depth) // Primitives, arrays, etc. use normal path
_ => ReadValue(context, destType) // Primitives, arrays, etc. use normal path
};
}
@ -329,7 +329,7 @@ public static partial class AcBinaryDeserializer
if (TryReadAndSetTypedValue(context, target, propInfo, peekCode))
return;
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
var value = ReadValue(context, propInfo.PropertyType);
propInfo.SetValue(target, value);
}
catch (InvalidCastException ex)

View File

@ -100,14 +100,12 @@ public static partial class AcBinaryDeserializer
/// UseMetadata=true: cacheMap[i] gives the setter (null → skip).
/// UseMetadata=false: properties[i] gives the setter directly.
/// </summary>
private static void PopulateObjectPropertiesIndexed<TInput>(
BinaryDeserializationContext<TInput> context,
object target,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth,
bool skipDefaultWrite)
private static void PopulateObjectPropertiesIndexed<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
where TInput : struct, IBinaryInputBase
{
// No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
// already prevents pathological depth in well-formed payloads. Malicious wire is out of scope.
var metadata = wrapper.Metadata;
var properties = metadata.PropertiesArray;
var cacheMap = wrapper.CacheMap;
@ -246,7 +244,7 @@ public static partial class AcBinaryDeserializer
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth);
var nestedValue = ReadValue(context, propInfo.PropertyType);
if (nestedValue != null)
{
var complexIdx = propInfo.ComplexPropertyIndex;
@ -276,14 +274,14 @@ public static partial class AcBinaryDeserializer
{
// Marker already consumed — go straight to ReadObjectCoreWithWrapper
var propWrapper = ResolvePropertyWrapper(state.ParentWrapper, complexIdx, propInfo.PropertyType, context);
var value = ReadObjectCoreWithWrapper(context, propWrapper, nextDepth, cacheIndex: -1);
var value = ReadObjectCoreWithWrapper(context, propWrapper, cacheIndex: -1);
propInfo.SetValue(target, value);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
var value = ReadValue(context, propInfo.PropertyType);
propInfo.SetValue(target, value);
}
}
@ -365,10 +363,12 @@ public static partial class AcBinaryDeserializer
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PopulateObject<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
private static void PopulateObject<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, bool skipDefaultWrite)
where TInput : struct, IBinaryInputBase
{
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
// Bridge: PopulateObjectPropertiesIndexed still has int depth in signature (other callers use it).
// Pass 0 as placeholder; depth-cleanup of PopulateObjectPropertiesIndexed + its other callers is pending.
PopulateObjectPropertiesIndexed(context, target, wrapper, 0, skipDefaultWrite);
}
#endregion
@ -393,7 +393,7 @@ public static partial class AcBinaryDeserializer
for (int i = 0; i < count; i++)
{
// ReadValue handles ChainMode internally (ReadObject returns cached instance)
var value = ReadValue(context, elementType, nextDepth);
var value = ReadValue(context, elementType);
targetList.Add(value);
}
}
@ -461,13 +461,13 @@ public static partial class AcBinaryDeserializer
object? value;
if (typeCode == BinaryTypeCode.Object && elementMetadata != null)
{
value = ReadObjectCoreWithWrapper(context, wrapper, nextDepth, cacheIndex: cacheIndex);
value = ReadObjectCoreWithWrapper(context, wrapper, cacheIndex: cacheIndex);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
value = ReadValue(context, elementType, nextDepth);
value = ReadValue(context, elementType);
}
if (i < existingCount)
@ -558,7 +558,7 @@ public static partial class AcBinaryDeserializer
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
newItem = ReadValue(context, elementType, nextDepth);
newItem = ReadValue(context, elementType);
if (newItem == null) continue;
}
@ -667,7 +667,7 @@ public static partial class AcBinaryDeserializer
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
newItem = ReadValue(context, elementType, nextDepth);
newItem = ReadValue(context, elementType);
if (newItem == null) continue;
}

View File

@ -72,7 +72,7 @@ public static partial class AcBinaryDeserializer
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
// Type dispatch table for fast ReadValue — generic per TInput, JIT specializes each
private delegate object? TypeReader<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private delegate object? TypeReader<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase;
private static class TypeReaderTable<TInput> where TInput : struct, IBinaryInputBase
@ -82,37 +82,37 @@ public static partial class AcBinaryDeserializer
private static TypeReader<TInput>?[] InitReaders()
{
var readers = new TypeReader<TInput>?[byte.MaxValue + 1];
readers[BinaryTypeCode.Null] = static (_, _, _) => null;
readers[BinaryTypeCode.True] = static (_, _, _) => true;
readers[BinaryTypeCode.False] = static (_, _, _) => false;
readers[BinaryTypeCode.Int8] = static (ctx, _, _) => (sbyte)ctx.ReadByte();
readers[BinaryTypeCode.UInt8] = static (ctx, _, _) => ctx.ReadByte();
readers[BinaryTypeCode.Int16] = static (ctx, _, _) => ctx.ReadInt16Unsafe();
readers[BinaryTypeCode.UInt16] = static (ctx, _, _) => ctx.ReadUInt16Unsafe();
readers[BinaryTypeCode.Int32] = static (ctx, type, _) => ReadInt32Value(ctx, type);
readers[BinaryTypeCode.UInt32] = static (ctx, _, _) => ctx.ReadVarUInt();
readers[BinaryTypeCode.Int64] = static (ctx, _, _) => ctx.ReadVarLong();
readers[BinaryTypeCode.UInt64] = static (ctx, _, _) => ctx.ReadVarULong();
readers[BinaryTypeCode.Float32] = static (ctx, _, _) => ctx.ReadSingleUnsafe();
readers[BinaryTypeCode.Float64] = static (ctx, _, _) => ctx.ReadDoubleUnsafe();
readers[BinaryTypeCode.Decimal] = static (ctx, _, _) => ctx.ReadDecimalUnsafe();
readers[BinaryTypeCode.Char] = static (ctx, _, _) => ctx.ReadCharUnsafe();
readers[BinaryTypeCode.Null] = static (_, _) => null;
readers[BinaryTypeCode.True] = static (_, _) => true;
readers[BinaryTypeCode.False] = static (_, _) => false;
readers[BinaryTypeCode.Int8] = static (ctx, _) => (sbyte)ctx.ReadByte();
readers[BinaryTypeCode.UInt8] = static (ctx, _) => ctx.ReadByte();
readers[BinaryTypeCode.Int16] = static (ctx, _) => ctx.ReadInt16Unsafe();
readers[BinaryTypeCode.UInt16] = static (ctx, _) => ctx.ReadUInt16Unsafe();
readers[BinaryTypeCode.Int32] = static (ctx, type) => ReadInt32Value(ctx, type);
readers[BinaryTypeCode.UInt32] = static (ctx, _) => ctx.ReadVarUInt();
readers[BinaryTypeCode.Int64] = static (ctx, _) => ctx.ReadVarLong();
readers[BinaryTypeCode.UInt64] = static (ctx, _) => ctx.ReadVarULong();
readers[BinaryTypeCode.Float32] = static (ctx, _) => ctx.ReadSingleUnsafe();
readers[BinaryTypeCode.Float64] = static (ctx, _) => ctx.ReadDoubleUnsafe();
readers[BinaryTypeCode.Decimal] = static (ctx, _) => ctx.ReadDecimalUnsafe();
readers[BinaryTypeCode.Char] = static (ctx, _) => ctx.ReadCharUnsafe();
// H2Q6 non-ASCII tier readers (Compact mode): fixed-width header [charLen][utf8Len] + 1-pass decode.
// FastWire mode dispatches the StringSmall (=91) marker through the same handler — see ReadStringSmall.
readers[BinaryTypeCode.StringSmall] = static (ctx, _, _) => ReadStringSmall(ctx);
readers[BinaryTypeCode.StringMedium] = static (ctx, _, _) => ReadStringMedium(ctx);
readers[BinaryTypeCode.StringBig] = static (ctx, _, _) => ReadStringBig(ctx);
readers[BinaryTypeCode.StringInterned] = static (ctx, _, _) => ctx.GetInternedString((int)ctx.ReadVarUInt());
readers[BinaryTypeCode.StringEmpty] = static (_, _, _) => string.Empty;
readers[BinaryTypeCode.StringSmall] = static (ctx, _) => ReadStringSmall(ctx);
readers[BinaryTypeCode.StringMedium] = static (ctx, _) => ReadStringMedium(ctx);
readers[BinaryTypeCode.StringBig] = static (ctx, _) => ReadStringBig(ctx);
readers[BinaryTypeCode.StringInterned] = static (ctx, _) => ctx.GetInternedString((int)ctx.ReadVarUInt());
readers[BinaryTypeCode.StringEmpty] = static (_, _) => string.Empty;
// H2Q6 interning tier readers (Compact mode only — Big tier never engages on interning path)
readers[BinaryTypeCode.StringInternFirstSmall] = static (ctx, _, _) => ReadAndRegisterInternedStringSmall(ctx);
readers[BinaryTypeCode.StringInternFirstMedium] = static (ctx, _, _) => ReadAndRegisterInternedStringMedium(ctx);
readers[BinaryTypeCode.StringAscii] = static (ctx, _, _) => ReadPlainStringAscii(ctx);
readers[BinaryTypeCode.DateTime] = static (ctx, _, _) => ctx.ReadDateTimeUnsafe();
readers[BinaryTypeCode.DateTimeOffset] = static (ctx, _, _) => ctx.ReadDateTimeOffsetUnsafe();
readers[BinaryTypeCode.TimeSpan] = static (ctx, _, _) => ctx.ReadTimeSpanUnsafe();
readers[BinaryTypeCode.Guid] = static (ctx, _, _) => ctx.ReadGuidUnsafe();
readers[BinaryTypeCode.Enum] = static (ctx, type, _) => ReadEnumValue(ctx, type);
readers[BinaryTypeCode.StringInternFirstSmall] = static (ctx, _) => ReadAndRegisterInternedStringSmall(ctx);
readers[BinaryTypeCode.StringInternFirstMedium] = static (ctx, _) => ReadAndRegisterInternedStringMedium(ctx);
readers[BinaryTypeCode.StringAscii] = static (ctx, _) => ReadPlainStringAscii(ctx);
readers[BinaryTypeCode.DateTime] = static (ctx, _) => ctx.ReadDateTimeUnsafe();
readers[BinaryTypeCode.DateTimeOffset] = static (ctx, _) => ctx.ReadDateTimeOffsetUnsafe();
readers[BinaryTypeCode.TimeSpan] = static (ctx, _) => ctx.ReadTimeSpanUnsafe();
readers[BinaryTypeCode.Guid] = static (ctx, _) => ctx.ReadGuidUnsafe();
readers[BinaryTypeCode.Enum] = static (ctx, type) => ReadEnumValue(ctx, type);
readers[BinaryTypeCode.Object] = ReadObject;
readers[BinaryTypeCode.ObjectRefFirst] = ReadObjectRefFirst;
readers[BinaryTypeCode.ObjectWithMetadata] = ReadObjectWithMetadata;
@ -124,7 +124,7 @@ public static partial class AcBinaryDeserializer
readers[BinaryTypeCode.ObjectWithTypeIndexRefFirst] = ReadObjectWithTypeIndexRefFirst;
readers[BinaryTypeCode.Array] = ReadArray;
readers[BinaryTypeCode.Dictionary] = ReadDictionary;
readers[BinaryTypeCode.ByteArray] = static (ctx, _, _) => ReadByteArray(ctx);
readers[BinaryTypeCode.ByteArray] = static (ctx, _) => ReadByteArray(ctx);
// V4N5 cleanup (2026-05-06): FixStr (UTF-8 short non-ASCII, 103..134) range REMOVED.
// Non-ASCII short strings now use StringSmall tier marker (registered above).
@ -155,9 +155,9 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeReader<TInput> CreateFixStrAsciiReader<TInput>(int length) where TInput : struct, IBinaryInputBase
{
if (length == 0) return static (_, _, _) => string.Empty;
if (length == 0) return static (_, _) => string.Empty;
return (ctx, _, _) => ctx.ReadAsciiBytesAsString(length);
return (ctx, _) => ctx.ReadAsciiBytesAsString(length);
}
/// <summary>
@ -166,7 +166,7 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeReader<TInput> CreateFixObjReader<TInput>(int slot) where TInput : struct, IBinaryInputBase
{
return (ctx, targetType, depth) => ReadObjectFromSlot(ctx, slot, targetType, depth);
return (ctx, targetType) => ReadObjectFromSlot(ctx, slot, targetType);
}
//private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
@ -433,7 +433,7 @@ public static partial class AcBinaryDeserializer
try
{
context.ReadHeader();
return ReadValue(context, targetType, 0);
return ReadValue(context, targetType);
}
catch (AcBinaryDeserializationException) { throw; }
catch (Exception ex)
@ -1084,16 +1084,16 @@ public static partial class AcBinaryDeserializer
/// Reads typeCode + dispatches via TypeReaderTable — same as runtime ReadValue.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static object? ReadValueGenerated<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
internal static object? ReadValueGenerated<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
return ReadValue(context, targetType, depth);
return ReadValue(context, targetType);
}
/// <summary>
/// Optimized value reader using FrozenDictionary dispatch table.
/// </summary>
private static object? ReadValue<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadValue<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
if (context.IsAtEnd) return null;
@ -1131,7 +1131,7 @@ public static partial class AcBinaryDeserializer
var reader = TypeReaderTable<TInput>.Readers[typeCode];
if (reader != null)
{
return reader(context, targetType, depth);
return reader(context, targetType);
}
throw new AcBinaryDeserializationException(
@ -1439,7 +1439,7 @@ public static partial class AcBinaryDeserializer
/// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadObjectRef<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectRef<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var cacheIndex = (int)context.ReadVarUInt();
@ -1452,12 +1452,7 @@ public static partial class AcBinaryDeserializer
/// Subsequent: direct array access (~1-2ns).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadObjectFromSlot<TInput>(
BinaryDeserializationContext<TInput> context,
int slot,
Type targetType,
int depth)
where TInput : struct, IBinaryInputBase
private static object? ReadObjectFromSlot<TInput>(BinaryDeserializationContext<TInput> context, int slot, Type targetType) where TInput : struct, IBinaryInputBase
{
var wrapper = context.GetWrapper(targetType, slot);
@ -1470,20 +1465,20 @@ public static partial class AcBinaryDeserializer
{
var generatedReader = wrapper.GeneratedReader;
if (generatedReader != null)
return generatedReader.ReadObject(context, depth, cacheIndex: -1);
return generatedReader.ReadObject(context, cacheIndex: -1);
}
return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex: -1);
return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex: -1);
}
/// <summary>
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
/// Wire format: [Object][props...]
/// </summary>
private static object? ReadObject<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObject<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
return ReadObjectCore(context, targetType, depth, cacheIndex: -1);
return ReadObjectCore(context, targetType, cacheIndex: -1);
}
/// <summary>
@ -1491,11 +1486,11 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
/// </summary>
private static object? ReadObjectRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex);
return ReadObjectCore(context, targetType, cacheIndex: cacheIndex);
}
/// <summary>
@ -1504,7 +1499,7 @@ public static partial class AcBinaryDeserializer
/// Reads the runtime type name, resolves it, registers wrapper in poly slot cache,
/// then reads the inner marker via ReadValue.
/// </summary>
private static object? ReadObjectWithTypeName<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithTypeName<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var typeName = ReadPlainString(context);
@ -1515,7 +1510,7 @@ public static partial class AcBinaryDeserializer
var wrapper = context.GetWrapper(resolvedType);
context.RegisterPolymorphicWrapper(wrapper);
// Next byte is the actual inner marker (Object/Array/Dict/etc.) — read it via ReadValue
return ReadValue(context, resolvedType, depth);
return ReadValue(context, resolvedType);
}
/// <summary>
@ -1523,7 +1518,7 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectWithTypeNameRefFirst (69)] [TypeName string] [VarUInt refCacheIndex] [properties...]
/// Object body follows directly — no inner Object/ObjectRefFirst marker.
/// </summary>
private static object? ReadObjectWithTypeNameRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithTypeNameRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var typeName = ReadPlainString(context);
@ -1534,7 +1529,7 @@ public static partial class AcBinaryDeserializer
var wrapper = context.GetWrapper(resolvedType);
context.RegisterPolymorphicWrapper(wrapper);
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex);
return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex);
}
/// <summary>
@ -1543,12 +1538,12 @@ public static partial class AcBinaryDeserializer
/// Looks up the previously registered wrapper by index (~1-2ns array access),
/// then reads the inner marker via ReadValue.
/// </summary>
private static object? ReadObjectWithTypeIndex<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithTypeIndex<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var typeIndex = (int)context.ReadVarUInt();
var wrapper = context.GetPolymorphicWrapper(typeIndex);
return ReadValue(context, wrapper.Metadata.SourceType, depth);
return ReadValue(context, wrapper.Metadata.SourceType);
}
/// <summary>
@ -1556,26 +1551,28 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectWithTypeIndexRefFirst (71)] [VarUInt typeIndex] [VarUInt refCacheIndex] [properties...]
/// Object body follows directly — no inner Object/ObjectRefFirst marker. 0 dictionary lookup.
/// </summary>
private static object? ReadObjectWithTypeIndexRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithTypeIndexRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var typeIndex = (int)context.ReadVarUInt();
var wrapper = context.GetPolymorphicWrapper(typeIndex);
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex);
return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex);
}
/// <summary>
/// Object olvasás core implementáció.
/// </summary>
/// <param name="context"></param>
/// <param name="targetType"></param>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
private static object? ReadObjectCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth, int cacheIndex)
private static object? ReadObjectCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int cacheIndex)
where TInput : struct, IBinaryInputBase
{
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
{
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!);
}
var wrapper = context.GetWrapper(targetType);
@ -1588,16 +1585,16 @@ public static partial class AcBinaryDeserializer
{
var generatedReader = wrapper.GeneratedReader;
if (generatedReader != null)
return generatedReader.ReadObject(context, depth, cacheIndex);
return generatedReader.ReadObject(context, cacheIndex);
}
return ReadObjectCoreWithWrapper(context, wrapper, depth, cacheIndex);
return ReadObjectCoreWithWrapper(context, wrapper, cacheIndex);
}
/// <summary>
/// Object olvasás with pre-resolved wrapper (eliminates GetWrapper dictionary lookup).
/// </summary>
private static object? ReadObjectCoreWithWrapper<TInput>(BinaryDeserializationContext<TInput> context, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, int cacheIndex)
private static object? ReadObjectCoreWithWrapper<TInput>(BinaryDeserializationContext<TInput> context, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int cacheIndex)
where TInput : struct, IBinaryInputBase
{
var metadata = wrapper.Metadata;
@ -1611,7 +1608,7 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValueAt(cacheIndex, instance);
}
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
PopulateObject(context, instance, wrapper, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1637,10 +1634,10 @@ public static partial class AcBinaryDeserializer
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
/// </summary>
private static object? ReadObjectWithMetadata<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithMetadata<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1);
return ReadObjectWithMetadataCore(context, targetType, cacheIndex: -1);
}
/// <summary>
@ -1648,18 +1645,20 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
/// </summary>
private static object? ReadObjectWithMetadataRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadObjectWithMetadataRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex);
return ReadObjectWithMetadataCore(context, targetType, cacheIndex: cacheIndex);
}
/// <summary>
/// ObjectWithMetadata olvasás core implementáció.
/// </summary>
/// <param name="context"></param>
/// <param name="targetType"></param>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
private static object? ReadObjectWithMetadataCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth, int cacheIndex)
private static object? ReadObjectWithMetadataCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int cacheIndex)
where TInput : struct, IBinaryInputBase
{
// Inline metadata: propNameHash mindig jön
@ -1681,7 +1680,7 @@ public static partial class AcBinaryDeserializer
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
{
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!);
}
var wrapper = context.GetWrapper(targetType);
@ -1699,7 +1698,7 @@ public static partial class AcBinaryDeserializer
if (wrapper.CacheMap == null)
BuildCacheMap(wrapper, sourceHashes);
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
PopulateObject(context, instance, wrapper, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1784,14 +1783,13 @@ public static partial class AcBinaryDeserializer
#region Array Reading
private static object? ReadArray<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadArray<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
var elementType = GetCollectionElementType(targetType);
if (elementType == null) elementType = typeof(object);
var count = (int)context.ReadVarUInt();
var nextDepth = depth + 1;
// Optimized path for primitive arrays
if (targetType.IsArray && count > 0)
@ -1805,7 +1803,7 @@ public static partial class AcBinaryDeserializer
var array = Array.CreateInstance(elementType, count);
for (var i = 0; i < count; i++)
{
var value = ReadValue(context, elementType, nextDepth);
var value = ReadValue(context, elementType);
array.SetValue(value, i);
}
@ -1832,7 +1830,7 @@ public static partial class AcBinaryDeserializer
{
for (var i = 0; i < count; i++)
{
var value = ReadValue(context, elementType, nextDepth);
var value = ReadValue(context, elementType);
list.Add(value);
}
}
@ -2093,7 +2091,7 @@ public static partial class AcBinaryDeserializer
#region Dictionary Reading
private static object? ReadDictionary<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
private static object? ReadDictionary<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
where TInput : struct, IBinaryInputBase
{
if (!IsDictionaryType(targetType, out var keyType, out var valueType))
@ -2102,21 +2100,20 @@ public static partial class AcBinaryDeserializer
valueType = typeof(object);
}
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!);
}
private static object ReadDictionaryAsObject<TInput>(BinaryDeserializationContext<TInput> context, Type keyType, Type valueType, int depth)
private static object ReadDictionaryAsObject<TInput>(BinaryDeserializationContext<TInput> context, Type keyType, Type valueType)
where TInput : struct, IBinaryInputBase
{
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
var count = (int)context.ReadVarUInt();
var dict = (IDictionary)Activator.CreateInstance(dictType, count)!;
var nextDepth = depth + 1;
for (var i = 0; i < count; i++)
{
var key = ReadValue(context, keyType, nextDepth);
var value = ReadValue(context, valueType, nextDepth);
var key = ReadValue(context, keyType);
var value = ReadValue(context, valueType);
if (key != null)
dict.Add(key, value);
}

View File

@ -86,6 +86,14 @@ public static partial class AcBinarySerializer
private int _nextCacheIndex;
public int NextFirstIndex; // Next first occurrence index for scan pass. Direct access for performance.
/// <summary>
/// Global recursion depth counter — final safety net against pathological graphs and non-IId cycles.
/// Incremented/decremented at object-recursion entry/exit points (WriteObject runtime + generated WriteProperties/ScanObject).
/// Checked against <see cref="AcSerializerContextBase{TMetadata,TOptions}.MaxDepth"/> (byte, default 255).
/// Replaces the per-call <c>int depth</c> parameter passing — one byte field on context, ~5-7 cycles per object instead of per call.
/// </summary>
internal byte RecursionDepth;
#region WriteDuplicateEntry scan pass output for write pass cursor
private WriteDuplicateEntry[]? _writePlan;
@ -306,6 +314,7 @@ public static partial class AcBinarySerializer
_nextCacheIndex = 0;
NextFirstIndex = 0;
ScanVisitIndex = 0;
RecursionDepth = 0;
WritePlanCursor = 0;
WriteVisitIndex = 0;
_nextWritePlanVisitIndex = int.MaxValue;

View File

@ -31,19 +31,19 @@ public static partial class AcBinarySerializer
var genWriter = wrapper.GeneratedWriter;
if (genWriter != null && context.Options.UseGeneratedCode)
{
genWriter.ScanObject(value, context, 0);
genWriter.ScanObject(value, context);
context.SortWritePlan();
return;
}
ScanValue(value, wrapper, context, 0);
ScanValue(value, wrapper, context);
context.SortWritePlan();
}
private static void ScanValue<TOutput>(object? value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void ScanValue<TOutput>(object? value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
if (value == null || depth > context.MaxDepth)
if (value == null)
return;
var metadata = wrapper.Metadata;
@ -64,8 +64,6 @@ public static partial class AcBinarySerializer
if (metadata.ElementNeedsScan &&
!((isStringCollectionElementType = ReferenceEquals(metadata.CollectionElementType, StringType)) && !context.UseStringInterning))
{
var nextDepth = depth + 1;
if (isStringCollectionElementType)
{
ScanStringCollection(value, context);
@ -81,7 +79,7 @@ public static partial class AcBinarySerializer
{
var item = list[i];
if (item != null)
ScanItem(item, elementWrapper, context, nextDepth);
ScanItem(item, elementWrapper, context);
}
}
else if (value is IEnumerable enumerable)
@ -89,7 +87,7 @@ public static partial class AcBinarySerializer
foreach (var item in enumerable)
{
if (item != null)
ScanItem(item, elementWrapper, context, nextDepth);
ScanItem(item, elementWrapper, context);
}
}
}
@ -104,7 +102,7 @@ public static partial class AcBinarySerializer
var genWriter = wrapper.GeneratedWriter;
if (genWriter != null && context.Options.UseGeneratedCode)
{
genWriter.ScanObject(value, context, depth);
genWriter.ScanObject(value, context);
return;
}
@ -158,9 +156,17 @@ public static partial class AcBinarySerializer
}
// Fallback: runtime property loop (no SGen writer for this type)
// Global recursion depth safety net — only when ReferenceHandling != All
// (HasAllRefHandling=true tracks every type → 2nd-occurrence early-return above already prevents cycles).
var needsDepthCheck = !context.HasAllRefHandling;
if (needsDepthCheck)
{
if (context.RecursionDepth >= context.MaxDepth) throw new InvalidOperationException($"AcBinary scan: recursion depth exceeded MaxDepth={context.MaxDepth}");
context.RecursionDepth++;
}
var refProperties = metadata.ReferenceProperties;
var hasPropertyFilter = context.HasPropertyFilter;
var nextDepth2 = depth + 1;
for (var i = 0; i < refProperties.Length; i++)
{
@ -199,10 +205,12 @@ public static partial class AcBinarySerializer
propWrapper = context.GetWrapper(runtimeType);
wrapper.SetPropertyTypeWrapper(prop.ComplexPropertyIndex, propWrapper);
}
ScanValue(propValue, propWrapper, context, nextDepth2);
ScanValue(propValue, propWrapper, context);
}
}
}
if (needsDepthCheck) context.RecursionDepth--;
}
/// <summary>
@ -218,12 +226,13 @@ public static partial class AcBinarySerializer
internal static void ScanValueGenerated<TOutput>(
object value,
[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type,
BinarySerializationContext<TOutput> context,
int depth)
BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
// Bridge: ScanValue still has int depth in its signature (other internal callers use it).
// Pass 0 placeholder; full depth-cleanup of ScanValue chain is pending.
var wrapper = context.GetWrapper(type);
ScanValue(value, wrapper, context, depth);
ScanValue(value, wrapper, context);
}
/// <summary>
@ -255,12 +264,12 @@ public static partial class AcBinarySerializer
/// falls back to GetWrapper for polymorphic items.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ScanItem<TOutput>(object item, TypeMetadataWrapper<BinarySerializeTypeMetadata>? elementWrapper, BinarySerializationContext<TOutput> context, int depth)
private static void ScanItem<TOutput>(object item, TypeMetadataWrapper<BinarySerializeTypeMetadata>? elementWrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
var itemType = item.GetType();
var itemWrapper = itemType == elementWrapper!.Metadata.SourceType ? elementWrapper : context.GetWrapper(itemType);
ScanValue(item, itemWrapper, context, depth);
ScanValue(item, itemWrapper, context);
}
}

View File

@ -101,7 +101,7 @@ public static partial class AcBinarySerializer
// Run serialization to trigger callbacks
context.WriteHeader();
WriteValue(value, runtimeType, context, 0);
WriteValue(value, runtimeType, context);
return result;
}
@ -328,7 +328,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteObject(value, wrapper, context, 0);
WriteObject(value, wrapper, context);
if (options.UseCompression != Lz4CompressionMode.None)
return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression);
@ -340,7 +340,7 @@ public static partial class AcBinarySerializer
var actualValue = ConvertExpressionValue(value, ref runtimeType);
ScanForDuplicates(actualValue, runtimeType, context);
context.WriteHeader();
WriteValue(actualValue, runtimeType, context, 0);
WriteValue(actualValue, runtimeType, context);
if (options.UseCompression != Lz4CompressionMode.None)
return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression);
@ -374,7 +374,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteObject(value, wrapper, context, 0);
WriteObject(value, wrapper, context);
if (options.UseCompression != Lz4CompressionMode.None)
return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression);
@ -385,7 +385,7 @@ public static partial class AcBinarySerializer
var actualValue = ConvertExpressionValue(value, ref runtimeType);
ScanForDuplicates(actualValue, runtimeType, context);
context.WriteHeader();
WriteValue(actualValue, runtimeType, context, 0);
WriteValue(actualValue, runtimeType, context);
if (options.UseCompression != Lz4CompressionMode.None)
return Lz4.Compress(context.Output.AsSpan(context._buffer, context._position), options.UseCompression);
@ -447,7 +447,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteObject(value, wrapper, context, 0);
WriteObject(value, wrapper, context);
if (options.UseCompression != Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithBufferWriter(context);
@ -462,7 +462,7 @@ public static partial class AcBinarySerializer
var actualValue = ConvertExpressionValue(value, ref runtimeType);
ScanForDuplicates(actualValue, runtimeType, context);
context.WriteHeader();
WriteValue(actualValue, runtimeType, context, 0);
WriteValue(actualValue, runtimeType, context);
if (options.UseCompression != Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithBufferWriter(context);
@ -507,7 +507,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteObject(value, wrapper, context, 0);
WriteObject(value, wrapper, context);
if (options.UseCompression != Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithBufferWriter(context);
@ -521,7 +521,7 @@ public static partial class AcBinarySerializer
var actualValue = ConvertExpressionValue(value, ref runtimeType);
ScanForDuplicates(actualValue, runtimeType, context);
context.WriteHeader();
WriteValue(actualValue, runtimeType, context, 0);
WriteValue(actualValue, runtimeType, context);
if (options.UseCompression != Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithBufferWriter(context);
@ -721,7 +721,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteObject(value, wrapper, context, 0);
WriteObject(value, wrapper, context);
if (options.UseCompression != Compression.Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithPipeWriter(context);
@ -735,7 +735,7 @@ public static partial class AcBinarySerializer
var actualValue = ConvertExpressionValue(value, ref runtimeType);
ScanForDuplicates(actualValue, runtimeType, context);
context.WriteHeader();
WriteValue(actualValue, runtimeType, context, 0);
WriteValue(actualValue, runtimeType, context);
if (options.UseCompression != Compression.Lz4CompressionMode.None)
ThrowCompressionNotSupportedWithPipeWriter(context);
@ -766,7 +766,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteValue(value, runtimeType, context, 0);
WriteValue(value, runtimeType, context);
return context.Position;
}
finally
@ -791,7 +791,7 @@ public static partial class AcBinarySerializer
{
ScanForDuplicates(value, runtimeType, context);
context.WriteHeader();
WriteValue(value, runtimeType, context, 0);
WriteValue(value, runtimeType, context);
// If compression enabled, compress directly from buffer span (1 allocation)
if (options.UseCompression != Lz4CompressionMode.None)
@ -891,10 +891,10 @@ public static partial class AcBinarySerializer
/// Equivalent to the runtime's WriteValueNonPrimitive path.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WriteValueGenerated<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context, int depth)
internal static void WriteValueGenerated<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
WriteValueNonPrimitive(value, type, context, depth);
WriteValueNonPrimitive(value, type, context);
}
/// <summary>
@ -930,17 +930,11 @@ public static partial class AcBinarySerializer
/// Uses pre-resolved wrapper type to avoid GetWrapper dictionary lookup.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WriteObjectGenerated<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context, int depth)
internal static void WriteObjectGenerated<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
if (depth > context.MaxDepth)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
var wrapper = context.GetWrapper(type);
WriteObject(value, wrapper, context, depth);
WriteObject(value, wrapper, context);
}
/// <summary>
@ -949,24 +943,18 @@ public static partial class AcBinarySerializer
/// First call per slot per context: populates slot from GetWrapper. Subsequent calls: direct array index.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WriteObjectGenerated<TOutput>(object value, Type type, int wrapperSlot, BinarySerializationContext<TOutput> context, int depth)
internal static void WriteObjectGenerated<TOutput>(object value, Type type, int wrapperSlot, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
if (depth > context.MaxDepth)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
var wrapper = context.GetWrapper(type, wrapperSlot);
WriteObject(value, wrapper, context, depth);
WriteObject(value, wrapper, context);
}
#endregion
#region Value Writing
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteValue<TOutput>(object? value, Type type, BinarySerializationContext<TOutput> context, int depth)
private static void WriteValue<TOutput>(object? value, Type type, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
if (value == null)
@ -979,7 +967,7 @@ public static partial class AcBinarySerializer
if (TryWritePrimitive(value, type, context))
return;
WriteValueNonPrimitive(value, type, context, depth);
WriteValueNonPrimitive(value, type, context);
}
/// <summary>
@ -987,7 +975,7 @@ public static partial class AcBinarySerializer
/// Skips null check and TryWritePrimitive — caller guarantees value is non-null and not a primitive type.
/// Called from WritePropertyOrSkip default case (PropertyAccessorType.Object) and WriteValue fallback.
/// </summary>
private static void WriteValueNonPrimitive<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context, int depth)
private static void WriteValueNonPrimitive<TOutput>(object value, Type type, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
// Nullable<T> where T is a value type: boxed value may be a primitive.
@ -998,12 +986,6 @@ public static partial class AcBinarySerializer
return;
}
if (depth > context.MaxDepth)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
// Handle byte arrays specially (value-like, no reference tracking)
if (value is byte[] byteArray)
{
@ -1014,7 +996,7 @@ public static partial class AcBinarySerializer
// Handle dictionaries
if (value is IDictionary dictionary)
{
WriteDictionary(dictionary, context, depth);
WriteDictionary(dictionary, context);
return;
}
@ -1024,19 +1006,19 @@ public static partial class AcBinarySerializer
// Handle collections/arrays
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
WriteArray(enumerable, wrapper, context, depth);
WriteArray(enumerable, wrapper, context);
return;
}
// Handle complex objects with single-pass reference tracking
WriteObject(value, wrapper, context, depth);
WriteObject(value, wrapper, context);
}
/// <summary>
/// Writes a non-primitive value with a pre-resolved wrapper (from PropertyTypeWrappers cache).
/// Avoids GetWrapper dictionary lookup. Handles byte[], dictionary, collection, and complex objects.
/// </summary>
private static void WriteValueNonPrimitiveWithWrapper<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WriteValueNonPrimitiveWithWrapper<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
var type = wrapper.Metadata.SourceType;
@ -1048,12 +1030,6 @@ public static partial class AcBinarySerializer
return;
}
if (depth > context.MaxDepth)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
// Handle byte arrays specially (value-like, no reference tracking)
if (value is byte[] byteArray)
{
@ -1064,19 +1040,19 @@ public static partial class AcBinarySerializer
// Handle dictionaries
if (value is IDictionary dictionary)
{
WriteDictionary(dictionary, context, depth);
WriteDictionary(dictionary, context);
return;
}
// Handle collections/arrays
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
WriteArray(enumerable, wrapper, context, depth);
WriteArray(enumerable, wrapper, context);
return;
}
// Handle complex objects
WriteObject(value, wrapper, context, depth);
WriteObject(value, wrapper, context);
}
/// <summary>
@ -1085,7 +1061,7 @@ public static partial class AcBinarySerializer
/// delegates to WriteObjectPolymorphic for combined poly+ref marker handling.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void WriteValueNonPrimitiveWithWrapperPoly<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth, Type polyRuntimeType)
private static void WriteValueNonPrimitiveWithWrapperPoly<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, Type polyRuntimeType)
where TOutput : struct, IBinaryOutputBase
{
var type = wrapper.Metadata.SourceType;
@ -1096,13 +1072,6 @@ public static partial class AcBinarySerializer
return;
}
if (depth > context.MaxDepth)
{
context.WritePolymorphicPrefix(polyRuntimeType);
context.WriteByte(BinaryTypeCode.Null);
return;
}
if (value is byte[] byteArray)
{
context.WritePolymorphicPrefix(polyRuntimeType);
@ -1113,19 +1082,19 @@ public static partial class AcBinarySerializer
if (value is IDictionary dictionary)
{
context.WritePolymorphicPrefix(polyRuntimeType);
WriteDictionary(dictionary, context, depth);
WriteDictionary(dictionary, context);
return;
}
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
context.WritePolymorphicPrefix(polyRuntimeType);
WriteArray(enumerable, wrapper, context, depth);
WriteArray(enumerable, wrapper, context);
return;
}
// Complex object — handles combined poly+ref markers
WriteObjectPolymorphic(value, wrapper, context, depth, polyRuntimeType);
WriteObjectPolymorphic(value, wrapper, context, polyRuntimeType);
}
/// <summary>
@ -1501,7 +1470,7 @@ public static partial class AcBinarySerializer
#region Complex Type Writers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObject<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WriteObject<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
var metadata = wrapper.Metadata;
@ -1511,9 +1480,9 @@ public static partial class AcBinarySerializer
if (context.UseTypeReferenceHandling(metadata))
{
if (useMetaForType)
WriteObjectWithRefHandlingMeta(value, wrapper, context, depth);
WriteObjectWithRefHandlingMeta(value, wrapper, context);
else
WriteObjectWithRefHandling(value, wrapper, context, depth);
WriteObjectWithRefHandling(value, wrapper, context);
return;
}
@ -1540,7 +1509,7 @@ public static partial class AcBinarySerializer
context.WriteByte(BinaryTypeCode.Object);
}
WriteObjectProperties(value, wrapper, context, depth, useMetaForType);
WriteObjectProperties(value, wrapper, context, useMetaForType);
}
/// <summary>
@ -1548,7 +1517,7 @@ public static partial class AcBinarySerializer
/// Cold path: only IId types with ref tracking enabled.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObjectWithRefHandling<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WriteObjectWithRefHandling<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
// Reference handling: consume pre-computed write plan entry from scan pass cursor
@ -1586,7 +1555,7 @@ public static partial class AcBinarySerializer
context.WriteByte(BinaryTypeCode.Object);
}
WriteObjectProperties(value, wrapper, context, depth, useMetaForType: false);
WriteObjectProperties(value, wrapper, context, useMetaForType: false);
}
/// <summary>
@ -1594,7 +1563,7 @@ public static partial class AcBinarySerializer
/// Cold path: IId types with ref tracking + UseMetadata enabled.
/// </summary>
//[MethodImpl(MethodImplOptions.NoInlining)]
private static void WriteObjectWithRefHandlingMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WriteObjectWithRefHandlingMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
var isFirstMetadataOccurrence = BinarySerializationContext<TOutput>.RegisterMetadataType(wrapper);
@ -1627,39 +1596,49 @@ public static partial class AcBinarySerializer
}
context.WriteInlineMetadata(wrapper.Metadata, isFirstMetadataOccurrence);
WriteObjectProperties(value, wrapper, context, depth, useMetaForType: true);
WriteObjectProperties(value, wrapper, context, useMetaForType: true);
}
/// <summary>
/// Shared property writing loop — used by WriteObject, WriteObjectWithRefHandling, WriteObjectPolymorphic.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteObjectProperties<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth, bool useMetaForType)
private static void WriteObjectProperties<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, bool useMetaForType)
where TOutput : struct, IBinaryOutputBase
{
var nextDepth = depth + 1;
if (context.UseGeneratedCode)
{
var generatedWriter = wrapper.GeneratedWriter;
if (generatedWriter != null)
{
generatedWriter.WriteProperties(value, context, nextDepth);
// SGen path handles its own RecursionDepth inc/dec via generated emit (gated on !HasAllRefHandling)
generatedWriter.WriteProperties(value, context);
return;
}
}
// Runtime path: global recursion depth safety net — only when ReferenceHandling != All
// (HasAllRefHandling=true tracks every type → write plan already prevents cycles via ObjectRef).
var needsDepthCheck = !context.HasAllRefHandling;
if (needsDepthCheck)
{
if (context.RecursionDepth >= context.MaxDepth) throw new InvalidOperationException($"AcBinary serialize: recursion depth exceeded MaxDepth={context.MaxDepth}");
context.RecursionDepth++;
}
if (!useMetaForType)
{
WritePropertiesMarkerless(value, wrapper, context, nextDepth);
WritePropertiesMarkerless(value, wrapper, context);
}
else
{
WritePropertiesWithMeta(value, wrapper, context, nextDepth);
WritePropertiesWithMeta(value, wrapper, context);
}
if (needsDepthCheck) context.RecursionDepth--;
}
private static void WritePropertiesWithMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int nextDepth) where TOutput : struct, IBinaryOutputBase
private static void WritePropertiesWithMeta<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase
{
var properties = wrapper.Metadata.Properties;
var propCount = properties.Length;
@ -1679,12 +1658,12 @@ public static partial class AcBinarySerializer
continue;
}
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
WritePropertyOrSkip(value, prop, wrapper, context);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WritePropertiesMarkerless<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int nextDepth) where TOutput : struct, IBinaryOutputBase
private static void WritePropertiesMarkerless<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context) where TOutput : struct, IBinaryOutputBase
{
var properties = wrapper.Metadata.Properties;
var propCount = properties.Length;
@ -1701,7 +1680,7 @@ public static partial class AcBinarySerializer
}
else
{
WritePropertyOrSkip(value, prop, wrapper, context, nextDepth);
WritePropertyOrSkip(value, prop, wrapper, context);
}
}
}
@ -1742,7 +1721,7 @@ public static partial class AcBinarySerializer
/// Poly always implies UseMetadata=false (checked in WritePropertyOrSkip).
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private static void WriteObjectPolymorphic<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth, Type polyRuntimeType)
private static void WriteObjectPolymorphic<TOutput>(object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, Type polyRuntimeType)
where TOutput : struct, IBinaryOutputBase
{
var metadata = wrapper.Metadata;
@ -1769,7 +1748,7 @@ public static partial class AcBinarySerializer
// Poly marker (handles combined poly+ref)
WritePolymorphicMarker(context, polyRuntimeType, cachedObjectCacheIndex);
WriteObjectProperties(value, wrapper, context, depth, false);
WriteObjectProperties(value, wrapper, context, false);
}
/// <summary>
@ -1916,7 +1895,7 @@ public static partial class AcBinarySerializer
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2072",
Justification = "Polymorphism via obj.GetType() is a documented trimmer blind spot. Consumers must root "
+ "polymorphic concrete types via [AcBinarySerializable] (SGen) or TrimmerRootAssembly.")]
private static void WritePropertyOrSkip<TOutput>(object obj, BinaryPropertyAccessor prop, TypeMetadataWrapper<BinarySerializeTypeMetadata> parentWrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WritePropertyOrSkip<TOutput>(object obj, BinaryPropertyAccessor prop, TypeMetadataWrapper<BinarySerializeTypeMetadata> parentWrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
switch (prop.AccessorType)
@ -2013,16 +1992,16 @@ public static partial class AcBinarySerializer
parentWrapper.SetPropertyTypeWrapper(complexIdx, propWrapper);
}
if (!context.UseMetadata && prop.IsPolymorphicCandidate && runtimeType != prop.PropertyType)
WriteValueNonPrimitiveWithWrapperPoly(value, propWrapper, context, depth, runtimeType);
WriteValueNonPrimitiveWithWrapperPoly(value, propWrapper, context, runtimeType);
else
WriteValueNonPrimitiveWithWrapper(value, propWrapper, context, depth);
WriteValueNonPrimitiveWithWrapper(value, propWrapper, context);
}
else
{
// Non-complex in default case (nullable value type, etc.)
if (!context.UseMetadata && prop.IsPolymorphicCandidate && runtimeType != prop.PropertyType)
context.WritePolymorphicPrefix(runtimeType);
WriteValueNonPrimitive(value, runtimeType, context, depth);
WriteValueNonPrimitive(value, runtimeType, context);
}
}
return;
@ -2037,11 +2016,10 @@ public static partial class AcBinarySerializer
/// <summary>
/// Optimized array writer with specialized paths for primitive collections.
/// </summary>
private static void WriteArray<TOutput>(IEnumerable enumerable, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context, int depth)
private static void WriteArray<TOutput>(IEnumerable enumerable, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
context.WriteByte(BinaryTypeCode.Array);
var nextDepth = depth + 1;
// Use pre-computed metadata — no GetWrapper or GetCollectionElementType needed
var metadata = wrapper.Metadata;
@ -2063,7 +2041,7 @@ public static partial class AcBinarySerializer
{
var item = list[i];
var itemType = item?.GetType() ?? typeof(object);
WriteValue(item, itemType, context, nextDepth);
WriteValue(item, itemType, context);
}
return;
}
@ -2075,7 +2053,7 @@ public static partial class AcBinarySerializer
foreach (var item in enumerable)
{
var itemType = item?.GetType() ?? typeof(object);
WriteValue(item, itemType, context, nextDepth);
WriteValue(item, itemType, context);
}
return;
}
@ -2091,7 +2069,7 @@ public static partial class AcBinarySerializer
foreach (var item in items)
{
var itemType = item?.GetType() ?? typeof(object);
WriteValue(item, itemType, context, nextDepth);
WriteValue(item, itemType, context);
}
}
@ -2376,22 +2354,21 @@ public static partial class AcBinarySerializer
return false;
}
private static void WriteDictionary<TOutput>(IDictionary dictionary, BinarySerializationContext<TOutput> context, int depth)
private static void WriteDictionary<TOutput>(IDictionary dictionary, BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase
{
context.WriteByte(BinaryTypeCode.Dictionary);
context.WriteVarUInt((uint)dictionary.Count);
var nextDepth = depth + 1;
foreach (DictionaryEntry entry in dictionary)
{
// Write key
var keyType = entry.Key?.GetType() ?? typeof(object);
WriteValue(entry.Key, keyType, context, nextDepth);
WriteValue(entry.Key, keyType, context);
// Write value
var valueType = entry.Value?.GetType() ?? typeof(object);
WriteValue(entry.Value, valueType, context, nextDepth);
WriteValue(entry.Value, valueType, context);
}
}

View File

@ -20,11 +20,10 @@ internal interface IGeneratedBinaryReader
/// UseMetadata=true falls back to runtime path (cross-type CacheMap not known at compile time).
/// </summary>
/// <param name="context">The deserialization context (owns buffer, position, options).</param>
/// <param name="depth">Current depth in the object graph.</param>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index for ref tracking.</param>
/// <typeparam name="TInput">Input strategy (ArrayBinaryInput or SequenceBinaryInput).</typeparam>
/// <returns>The deserialized object, or null if creation failed.</returns>
object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth, int cacheIndex)
object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int cacheIndex)
where TInput : struct, IBinaryInputBase;
/// <summary>
@ -34,8 +33,7 @@ internal interface IGeneratedBinaryReader
/// </summary>
/// <param name="value">The pre-created instance to populate. Implementation casts to the concrete type.</param>
/// <param name="context">The deserialization context (owns buffer, position, options).</param>
/// <param name="depth">Current depth in the object graph.</param>
/// <typeparam name="TInput">Input strategy (ArrayBinaryInput or SequenceBinaryInput).</typeparam>
void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int depth)
void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context)
where TInput : struct, IBinaryInputBase;
}

View File

@ -22,9 +22,8 @@ internal interface IGeneratedBinaryWriter
/// </summary>
/// <param name="value">The object whose properties to write. Implementation casts to the concrete type.</param>
/// <param name="context">The serialization context (owns buffer, position, options).</param>
/// <param name="depth">Current depth in the object graph (for nested object serialization).</param>
/// <typeparam name="TOutput">Output strategy (ArrayBinaryOutput or BufferWriterBinaryOutput).</typeparam>
void WriteProperties<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth)
void WriteProperties<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase;
/// <summary>
@ -33,7 +32,7 @@ internal interface IGeneratedBinaryWriter
/// Replaces the entire runtime ScanValue for SGen types — no GetWrapper, no delegate invoke.
/// Called from ScanForDuplicates or from parent SGen ScanObject (child).
/// </summary>
void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context, int depth)
void ScanObject<TOutput>(object value, AcBinarySerializer.BinarySerializationContext<TOutput> context)
where TOutput : struct, IBinaryOutputBase;
/// <summary>

View File

@ -12,17 +12,17 @@ public static partial class AcJsonDeserializer
#region With Reference Handling (JsonElement Path)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadValue(in JsonElement element, in Type targetType, DeserializationContext context, int depth)
private static object? ReadValue(in JsonElement element, in Type targetType, DeserializationContext context)
{
var kind = element.ValueKind;
if (kind == JsonValueKind.Object) return ReadObject(element, targetType, context, depth);
if (kind == JsonValueKind.Array) return ReadArray(element, targetType, context, depth);
if (kind == JsonValueKind.Object) return ReadObject(element, targetType, context);
if (kind == JsonValueKind.Array) return ReadArray(element, targetType, context);
if (kind == JsonValueKind.Null || kind == JsonValueKind.Undefined) return null;
return ReadPrimitive(element, targetType, kind);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadObject(in JsonElement element, in Type targetType, DeserializationContext context, int depth)
private static object? ReadObject(in JsonElement element, in Type targetType, DeserializationContext context)
{
// Check for $ref first - support both string (Newtonsoft) and int formats
if (element.TryGetProperty(RefPropertyUtf8, out var refElement))
@ -31,10 +31,8 @@ public static partial class AcJsonDeserializer
return context.TryGetReferencedObject(refId, out var refObj) ? refObj : new DeferredReference(refId, targetType);
}
if (depth > context.MaxDepth) return null;
if (IsDictionaryType(targetType, out var keyType, out var valueType))
return ReadDictionary(element, keyType!, valueType!, context, depth);
return ReadDictionary(element, keyType!, valueType!, context);
var metadata = GetTypeMetadata(targetType);
@ -56,7 +54,7 @@ public static partial class AcJsonDeserializer
if (element.TryGetProperty(IdPropertyUtf8, out var idElement))
context.RegisterObject(ParseRefId(idElement), instance);
PopulateObjectInternal(element, instance, metadata, context, depth);
PopulateObjectInternal(element, instance, metadata, context);
// ChainMode: Use cached IId info from metadata (no runtime reflection!)
if (context.IsChainMode && metadata.IsIId && metadata.IdGetter != null && metadata.IdType != null)
@ -122,10 +120,9 @@ public static partial class AcJsonDeserializer
}
}
private static void PopulateObjectInternal(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth)
private static void PopulateObjectInternal(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context)
{
var propsDict = metadata.PropertySettersFrozen;
var nextDepth = depth + 1;
foreach (var jsonProp in element.EnumerateObject())
{
@ -136,7 +133,7 @@ public static partial class AcJsonDeserializer
if (!propsDict.TryGetValue(propName, out var propInfo)) continue;
var value = ReadValue(jsonProp.Value, propInfo.PropertyType, context, nextDepth);
var value = ReadValue(jsonProp.Value, propInfo.PropertyType, context);
if (value is DeferredReference deferred)
context.AddPropertyToResolve(target, propInfo, deferred.RefId);
@ -145,11 +142,9 @@ public static partial class AcJsonDeserializer
}
}
private static void PopulateObjectInternalMerge(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context, int depth)
private static void PopulateObjectInternalMerge(in JsonElement element, object target, JsonDeserializeTypeMetadata metadata, DeserializationContext context)
{
var propsDict = metadata.PropertySettersFrozen;
var nextDepth = depth + 1;
var maxDepthReached = nextDepth > context.MaxDepth;
foreach (var jsonProp in element.EnumerateObject())
{
@ -163,23 +158,13 @@ public static partial class AcJsonDeserializer
var propValue = jsonProp.Value;
var propValueKind = propValue.ValueKind;
if (maxDepthReached)
{
if (propValueKind != JsonValueKind.Object && propValueKind != JsonValueKind.Array)
{
var primitiveValue = ReadPrimitive(propValue, propInfo.PropertyType, propValueKind);
propInfo.SetValue(target, primitiveValue);
}
continue;
}
// Handle IId collection merge
if (propInfo.IsIIdCollection && propValueKind == JsonValueKind.Array)
{
var existingCollection = propInfo.GetValue(target);
if (existingCollection != null)
{
MergeIIdCollection(propValue, existingCollection, propInfo, context, depth);
MergeIIdCollection(propValue, existingCollection, propInfo, context);
continue;
}
}
@ -190,7 +175,7 @@ public static partial class AcJsonDeserializer
// Check for $ref
if (propValue.TryGetProperty(RefPropertyUtf8, out _))
{
var value = ReadValue(propValue, propInfo.PropertyType, context, nextDepth);
var value = ReadValue(propValue, propInfo.PropertyType, context);
if (value is DeferredReference deferred)
context.AddPropertyToResolve(target, propInfo, deferred.RefId);
else
@ -205,13 +190,13 @@ public static partial class AcJsonDeserializer
if (existingObj != null)
{
var nestedMetadata = GetTypeMetadata(propInfo.PropertyType);
PopulateObjectInternalMerge(propValue, existingObj, nestedMetadata, context, nextDepth);
PopulateObjectInternalMerge(propValue, existingObj, nestedMetadata, context);
continue;
}
}
}
var value2 = ReadValue(propValue, propInfo.PropertyType, context, nextDepth);
var value2 = ReadValue(propValue, propInfo.PropertyType, context);
if (value2 is DeferredReference deferred2)
context.AddPropertyToResolve(target, propInfo, deferred2.RefId);
@ -220,10 +205,8 @@ public static partial class AcJsonDeserializer
}
}
private static void PopulateList(in JsonElement arrayElement, IList targetList, in Type listType, DeserializationContext context, int depth)
private static void PopulateList(in JsonElement arrayElement, IList targetList, in Type listType, DeserializationContext context)
{
if (depth > context.MaxDepth) return;
var elementType = GetCollectionElementType(listType);
if (elementType == null) return;
@ -233,7 +216,6 @@ public static partial class AcJsonDeserializer
try
{
targetList.Clear();
var nextDepth = depth + 1;
// ChainMode: Use cached IId info from element type metadata (no runtime reflection!)
JsonDeserializeTypeMetadata? elementMetadata = null;
@ -244,7 +226,7 @@ public static partial class AcJsonDeserializer
foreach (var item in arrayElement.EnumerateArray())
{
var value = ReadValue(item, elementType, context, nextDepth);
var value = ReadValue(item, elementType, context);
// ChainMode: Check if we already have this IId object using cached metadata
if (context.IsChainMode && value != null && elementMetadata != null &&
@ -270,20 +252,16 @@ public static partial class AcJsonDeserializer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadArray(in JsonElement element, in Type targetType, DeserializationContext context, int depth)
private static object? ReadArray(in JsonElement element, in Type targetType, DeserializationContext context)
{
if (depth > context.MaxDepth) return null;
var elementType = GetCollectionElementType(targetType);
if (elementType == null) return null;
var nextDepth = depth + 1;
if (targetType.IsArray)
{
var list = GetOrCreateListFactory(elementType)(0);
foreach (var item in element.EnumerateArray())
list.Add(ReadValue(item, elementType, context, nextDepth));
list.Add(ReadValue(item, elementType, context));
var array = Array.CreateInstance(elementType, list.Count);
list.CopyTo(array, 0);
@ -306,14 +284,14 @@ public static partial class AcJsonDeserializer
try
{
foreach (var item in element.EnumerateArray())
targetList.Add(ReadValue(item, elementType, context, nextDepth));
targetList.Add(ReadValue(item, elementType, context));
}
finally { acObservable?.EndUpdate(); }
return targetList;
}
private static void MergeIIdCollection(in JsonElement arrayElement, object existingCollection, PropertySetterInfo propInfo, DeserializationContext context, int depth)
private static void MergeIIdCollection(in JsonElement arrayElement, object existingCollection, PropertySetterInfo propInfo, DeserializationContext context)
{
var elementType = propInfo.ElementType!;
var idGetter = propInfo.ElementIdGetter!;
@ -343,7 +321,6 @@ public static partial class AcJsonDeserializer
}
}
var nextDepth = depth + 1;
foreach (var jsonItem in arrayElement.EnumerateArray())
{
if (jsonItem.ValueKind != JsonValueKind.Object) continue;
@ -357,23 +334,22 @@ public static partial class AcJsonDeserializer
if (existingById.TryGetValue(itemId, out var existingItem))
{
var itemMetadata = GetTypeMetadata(elementType);
PopulateObjectInternalMerge(jsonItem, existingItem, itemMetadata, context, nextDepth);
PopulateObjectInternalMerge(jsonItem, existingItem, itemMetadata, context);
continue;
}
}
var newItem = ReadValue(jsonItem, elementType, context, nextDepth);
var newItem = ReadValue(jsonItem, elementType, context);
if (newItem != null) existingList.Add(newItem);
}
}
finally { acObservable?.EndUpdate(); }
}
private static object ReadDictionary(in JsonElement element, in Type keyType, in Type valueType, DeserializationContext context, int depth)
private static object ReadDictionary(in JsonElement element, in Type keyType, in Type valueType, DeserializationContext context)
{
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
var dict = (IDictionary)Activator.CreateInstance(dictType)!;
var nextDepth = depth + 1;
foreach (var prop in element.EnumerateObject())
{
@ -388,7 +364,7 @@ public static partial class AcJsonDeserializer
else if (keyType.IsEnum) key = Enum.Parse(keyType, name);
else key = Convert.ChangeType(name, keyType, CultureInfo.InvariantCulture);
dict.Add(key, ReadValue(prop.Value, valueType, context, nextDepth));
dict.Add(key, ReadValue(prop.Value, valueType, context));
}
return dict;

View File

@ -71,7 +71,7 @@ public static partial class AcJsonDeserializer
var context = JsonDeserializationContextPool.Get(options);
try
{
var result = ReadValue(doc.RootElement, targetType, context, 0);
var result = ReadValue(doc.RootElement, targetType, context);
context.ResolveReferences();
return (T?)result;
}
@ -130,7 +130,7 @@ public static partial class AcJsonDeserializer
var context = JsonDeserializationContextPool.Get(options);
try
{
var result = ReadValue(doc.RootElement, targetType, context, 0);
var result = ReadValue(doc.RootElement, targetType, context);
context.ResolveReferences();
return (T?)result;
}
@ -197,7 +197,7 @@ public static partial class AcJsonDeserializer
var context = JsonDeserializationContextPool.Get(options);
try
{
var result = ReadValue(doc.RootElement, targetType, context, 0);
var result = ReadValue(doc.RootElement, targetType, context);
context.ResolveReferences();
return result;
}
@ -282,7 +282,7 @@ public static partial class AcJsonDeserializer
var context = JsonDeserializationContextPool.Get(options);
try
{
var result = ReadValue(doc.RootElement, targetType, context, 0);
var result = ReadValue(doc.RootElement, targetType, context);
context.ResolveReferences();
return result;
}
@ -398,7 +398,7 @@ public static partial class AcJsonDeserializer
if (rootElement.ValueKind == JsonValueKind.Array)
{
if (target is IList targetList)
PopulateList(rootElement, targetList, targetType, context, 0);
PopulateList(rootElement, targetList, targetType, context);
else
throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", json, targetType);
@ -409,7 +409,7 @@ public static partial class AcJsonDeserializer
if (rootElement.ValueKind == JsonValueKind.Object)
{
var metadata = GetTypeMetadata(targetType);
PopulateObjectInternalMerge(rootElement, target, metadata, context, 0);
PopulateObjectInternalMerge(rootElement, target, metadata, context);
}
else
throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", json, targetType);
@ -502,7 +502,7 @@ public static partial class AcJsonDeserializer
try
{
var result = ReadValue(doc.RootElement, targetType, context, 0);
var result = ReadValue(doc.RootElement, targetType, context);
context.ResolveReferences();
return new JsonDeserializeChain<T>(doc, context, chainTracker, (T?)result);
}
@ -523,14 +523,14 @@ public static partial class AcJsonDeserializer
if (rootElement.ValueKind == JsonValueKind.Array)
{
if (target is IList targetList)
PopulateList(rootElement, targetList, targetType, context, 0);
PopulateList(rootElement, targetList, targetType, context);
else
throw new AcJsonDeserializationException($"Cannot populate non-list target '{targetType.Name}' with JSON array", null, targetType);
}
else if (rootElement.ValueKind == JsonValueKind.Object)
{
var metadata = GetTypeMetadata(targetType);
PopulateObjectInternalMerge(rootElement, target, metadata, context, 0);
PopulateObjectInternalMerge(rootElement, target, metadata, context);
}
else
throw new AcJsonDeserializationException($"Cannot populate object with JSON value of kind '{rootElement.ValueKind}'", null, targetType);
@ -571,7 +571,7 @@ public static partial class AcJsonDeserializer
try
{
var result = ReadValue(_document.RootElement, targetType, _context, 0);
var result = ReadValue(_document.RootElement, targetType, _context);
_context.ResolveReferences();
return (TResult?)result;
}

View File

@ -127,7 +127,7 @@ public static partial class AcJsonSerializer
private static void ScanReferences(object? value, JsonSerializationContext context, int depth)
{
if (value == null || depth > context.MaxDepth) return;
if (value == null) return;
var type = value.GetType();
if (IsPrimitiveOrStringFast(type)) return;
@ -167,8 +167,6 @@ public static partial class AcJsonSerializer
if (TryWritePrimitive(value, type, context.Writer)) return;
if (depth > context.MaxDepth) { context.Writer.WriteNullValue(); return; }
if (value is IDictionary dictionary) { WriteDictionary(dictionary, context, depth); return; }
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType)) { WriteArray(enumerable, context, depth); return; }

View File

@ -25,7 +25,7 @@ public static partial class AcToonSerializer
context.WriteLine("@data {");
context.CurrentIndentLevel++;
WriteValue(value, type, context, 0);
WriteValue(value, type, context);
context.WriteLine();
context.CurrentIndentLevel--;
@ -35,7 +35,7 @@ public static partial class AcToonSerializer
/// <summary>
/// Write a value (dispatcher for different types).
/// </summary>
private static void WriteValue(object? value, Type type, ToonSerializationContext context, int depth)
private static void WriteValue(object? value, Type type, ToonSerializationContext context)
{
if (value == null)
{
@ -47,12 +47,6 @@ public static partial class AcToonSerializer
if (TryWritePrimitive(value, type, context))
return;
if (depth > context.MaxDepth)
{
context.Write("null");
return;
}
// Check for reference
if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryGetExistingRef(value, out var refId))
{
@ -63,19 +57,19 @@ public static partial class AcToonSerializer
// Handle dictionaries
if (value is IDictionary dictionary)
{
WriteDictionary(dictionary, context, depth);
WriteDictionary(dictionary, context);
return;
}
// Handle collections/arrays
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
WriteArray(enumerable, type, context, depth);
WriteArray(enumerable, type, context);
return;
}
// Handle complex objects
WriteObject(value, type, context, depth);
WriteObject(value, type, context);
}
/// <summary>
@ -223,7 +217,7 @@ public static partial class AcToonSerializer
/// <summary>
/// Write complex object.
/// </summary>
private static void WriteObject(object value, Type type, ToonSerializationContext context, int depth)
private static void WriteObject(object value, Type type, ToonSerializationContext context)
{
var metadata = GetTypeMetadata(type);
@ -243,8 +237,6 @@ public static partial class AcToonSerializer
context.WriteLine(" {");
context.CurrentIndentLevel++;
var nextDepth = depth + 1;
// Write properties
foreach (var prop in metadata.Properties)
{
@ -273,7 +265,7 @@ public static partial class AcToonSerializer
}
else
{
WriteValue(propValue, prop.PropertyType, context, nextDepth);
WriteValue(propValue, prop.PropertyType, context);
context.WriteLine();
}
}
@ -286,7 +278,7 @@ public static partial class AcToonSerializer
/// <summary>
/// Write array/collection.
/// </summary>
private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context, int depth)
private static void WriteArray(IEnumerable enumerable, Type type, ToonSerializationContext context)
{
var elementType = GetCollectionElementType(type) ?? typeof(object);
@ -312,12 +304,10 @@ public static partial class AcToonSerializer
context.WriteLine("[");
context.CurrentIndentLevel++;
var nextDepth = depth + 1;
foreach (var item in enumerable)
{
context.WriteIndent();
WriteValue(item, item?.GetType() ?? elementType, context, nextDepth);
WriteValue(item, item?.GetType() ?? elementType, context);
context.WriteLine();
}
@ -377,7 +367,7 @@ public static partial class AcToonSerializer
/// <summary>
/// Write dictionary.
/// </summary>
private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context, int depth)
private static void WriteDictionary(IDictionary dictionary, ToonSerializationContext context)
{
// Write dictionary header with count and type information
if (context.Options.ShowCollectionCount)
@ -390,21 +380,19 @@ public static partial class AcToonSerializer
context.WriteLine("{");
context.CurrentIndentLevel++;
var nextDepth = depth + 1;
foreach (DictionaryEntry entry in dictionary)
{
context.WriteIndent();
// Write key
var keyType = entry.Key?.GetType() ?? typeof(object);
WriteValue(entry.Key, keyType, context, nextDepth);
WriteValue(entry.Key, keyType, context);
context.Write(" => ");
// Write value
var valueType = entry.Value?.GetType() ?? typeof(object);
WriteValue(entry.Value, valueType, context, nextDepth);
WriteValue(entry.Value, valueType, context);
context.WriteLine();
}

View File

@ -285,7 +285,7 @@ public static partial class AcToonSerializer
private static void ScanReferences(object? value, ToonSerializationContext context, int depth)
{
if (value == null || depth > context.MaxDepth) return;
if (value == null) return;
var type = value.GetType();
if (IsPrimitiveOrStringFast(type)) return;