Inline dictionary serialization and scan codegen
Optimized AcBinarySourceGenerator to emit direct code for dictionary property serialization and scanning. Added PropInfo fields for dictionary value type metadata, scan requirements, and FNV-1a hashes. Specialized code now handles string interning, complex value reference tracking, and metadata inline, reducing runtime overhead and improving performance. Fallback to runtime methods only for unsupported types.
This commit is contained in:
parent
2aa2eecccd
commit
7902922195
|
|
@ -275,6 +275,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
string? dictValueTypeName = null;
|
||||
bool dictValueHasGenWriter = false;
|
||||
string? dictValueWriterClassName = null;
|
||||
bool dictValueIsIId = false;
|
||||
bool dictValueEnableMetadata = true;
|
||||
bool dictValueNeedsIdScan = true;
|
||||
bool dictValueNeedsAllRefScan = true;
|
||||
bool dictValueNeedsInternScan = true;
|
||||
int dictValueTypeNameHash = 0;
|
||||
int[]? dictValuePropertyHashes = null;
|
||||
if (kind == PropertyTypeKind.Dictionary)
|
||||
{
|
||||
var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type);
|
||||
|
|
@ -300,6 +307,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
dictValueWriterClassName = string.IsNullOrEmpty(vns)
|
||||
? $"{vfn}_GeneratedWriter"
|
||||
: $"{vns}.{vfn}_GeneratedWriter";
|
||||
|
||||
dictValueEnableMetadata = ReadEnableMetadata(resolvedValue);
|
||||
var dvScanFlags = ComputeNeedsScan(resolvedValue);
|
||||
dictValueNeedsIdScan = dvScanFlags.needsIdScan;
|
||||
dictValueNeedsAllRefScan = dvScanFlags.needsAllRefScan;
|
||||
dictValueNeedsInternScan = dvScanFlags.needsInternScan;
|
||||
var dvIidIface = resolvedValue.AllInterfaces.FirstOrDefault(ifc =>
|
||||
ifc.IsGenericType &&
|
||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||
dictValueIsIId = dvIidIface != null;
|
||||
dictValueTypeNameHash = ComputeFnvHash(resolvedValue.Name);
|
||||
dictValuePropertyHashes = ComputeChildPropertyHashes(resolvedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -316,6 +335,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
||||
collAddMethod, collHasCapacityCtor,
|
||||
dictKeyKind, dictValueKind, dictKeyTypeName, dictValueTypeName, dictValueHasGenWriter, dictValueWriterClassName,
|
||||
dictValueIsIId, dictValueEnableMetadata, dictValueTypeNameHash, dictValuePropertyHashes,
|
||||
dictValueNeedsIdScan, dictValueNeedsAllRefScan, dictValueNeedsInternScan,
|
||||
childTypeNameHash, childPropertyHashes,
|
||||
elementTypeNameHash, elementPropertyHashes,
|
||||
propEnableMetadata, elemEnableMetadata,
|
||||
|
|
@ -610,7 +631,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
// Hoist UseStringInterning + IsValidForInterningString checks if any string scanning needed
|
||||
var hasStringScan = scanProps.Any(p =>
|
||||
(p.TypeKind == PropertyTypeKind.String && p.InterningFlags != 0) ||
|
||||
(p.TypeKind == PropertyTypeKind.Collection && p.ElementKind == PropertyTypeKind.String && p.InterningFlags != 0));
|
||||
(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));
|
||||
|
||||
if (hasStringScan)
|
||||
{
|
||||
|
|
@ -745,14 +767,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
break;
|
||||
case PropertyTypeKind.Dictionary:
|
||||
// Dictionary: always runtime fallback via WriteValueGenerated (which calls WriteDictionary)
|
||||
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);");
|
||||
}
|
||||
else
|
||||
sb.AppendLine($"{i}AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
EmitDirectDictionaryWrite(sb, p, a, i);
|
||||
break;
|
||||
default:
|
||||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i);
|
||||
|
|
@ -849,14 +864,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
break;
|
||||
|
||||
case PropertyTypeKind.Dictionary:
|
||||
// Dictionary scan: runtime fallback via ScanValueGenerated
|
||||
if (p.IsNullable)
|
||||
{
|
||||
sb.AppendLine($"{i}if ({a} != null)");
|
||||
sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
}
|
||||
else
|
||||
sb.AppendLine($"{i}AcBinarySerializer.ScanValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
EmitScanDictionary(sb, p, a, i);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1073,6 +1081,126 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
// Primitive element collection — no scanning needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline dictionary scan. Iterates entries and:
|
||||
/// - String keys: ScanInternString if interning flags match
|
||||
/// - String values: ScanInternString if interning flags match
|
||||
/// - Complex+SGen values: ScanObject on each value (handles ref tracking internally)
|
||||
/// Eliminates GetWrapper dictionary lookup for all inlineable dictionary types.
|
||||
/// </summary>
|
||||
private static void EmitScanDictionary(StringBuilder sb, PropInfo p, string a, string i)
|
||||
{
|
||||
var s = p.Name;
|
||||
var hasStringKeys = p.DictKeyKind == PropertyTypeKind.String && p.InterningFlags != 0;
|
||||
var hasStringValues = p.DictValueKind == PropertyTypeKind.String && p.InterningFlags != 0;
|
||||
var hasComplexValues = p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter;
|
||||
|
||||
// No scanning needed for primitive-only dictionaries without internable strings or complex values
|
||||
if (!hasStringKeys && !hasStringValues && !hasComplexValues) return;
|
||||
|
||||
// Complex+SGen values: compile-time proven scan is no-op → skip entirely
|
||||
if (hasComplexValues && !p.DictValueNeedsScan && !hasStringKeys && !hasStringValues) return;
|
||||
|
||||
// Build guard expression for Complex+SGen values (3-axis: IId/AllRef/Intern)
|
||||
string? complexGuard = null;
|
||||
if (hasComplexValues && p.DictValueNeedsScan && !p.DictValueNeedsIdScan)
|
||||
{
|
||||
if (p.DictValueNeedsAllRefScan && p.DictValueNeedsInternScan)
|
||||
complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning";
|
||||
else if (p.DictValueNeedsAllRefScan)
|
||||
complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||
else if (p.DictValueNeedsInternScan)
|
||||
complexGuard = "context.UseStringInterning";
|
||||
}
|
||||
|
||||
// For string-only scan (no complex values), use simple interning loop
|
||||
if (!hasComplexValues)
|
||||
{
|
||||
sb.AppendLine($"{i}var sd_{s} = {a};");
|
||||
sb.AppendLine($"{i}if (sd_{s} != null && ({p.InterningFlags} & internBit) != 0)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} foreach (var sde_{s} in sd_{s})");
|
||||
sb.AppendLine($"{i} {{");
|
||||
if (hasStringKeys)
|
||||
{
|
||||
sb.AppendLine($"{i} if (sde_{s}.Key != null)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var sklen_{s} = sde_{s}.Key.Length;");
|
||||
sb.AppendLine($"{i} if (sklen_{s} >= minIntern && (maxIntern == 0 || sklen_{s} <= maxIntern))");
|
||||
sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Key);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
if (hasStringValues)
|
||||
{
|
||||
sb.AppendLine($"{i} if (sde_{s}.Value != null)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var svlen_{s} = sde_{s}.Value.Length;");
|
||||
sb.AppendLine($"{i} if (svlen_{s} >= minIntern && (maxIntern == 0 || svlen_{s} <= maxIntern))");
|
||||
sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Value);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Complex+SGen values (with optional string key/value interning)
|
||||
var writer = p.DictValueWriterClassName!;
|
||||
|
||||
// Guard entire scan block when no IId in value subtree
|
||||
if (complexGuard != null && !hasStringKeys && !hasStringValues)
|
||||
sb.AppendLine($"{i}if ({complexGuard})");
|
||||
|
||||
sb.AppendLine($"{i}{{");
|
||||
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} {{");
|
||||
|
||||
// String key interning
|
||||
if (hasStringKeys)
|
||||
{
|
||||
sb.AppendLine($"{i} if (({p.InterningFlags} & internBit) != 0 && sde_{s}.Key != null)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var sklen_{s} = sde_{s}.Key.Length;");
|
||||
sb.AppendLine($"{i} if (sklen_{s} >= minIntern && (maxIntern == 0 || sklen_{s} <= maxIntern))");
|
||||
sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Key);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
|
||||
// String value interning
|
||||
if (hasStringValues)
|
||||
{
|
||||
sb.AppendLine($"{i} if (({p.InterningFlags} & internBit) != 0 && sde_{s}.Value != null)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var svlen_{s} = sde_{s}.Value.Length;");
|
||||
sb.AppendLine($"{i} if (svlen_{s} >= minIntern && (maxIntern == 0 || svlen_{s} <= maxIntern))");
|
||||
sb.AppendLine($"{i} context.ScanInternString(sde_{s}.Value);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
|
||||
// Complex value ScanObject
|
||||
if (hasComplexValues)
|
||||
{
|
||||
sb.AppendLine($"{i} if (sde_{s}.Value != null)");
|
||||
if (complexGuard != null)
|
||||
{
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if ({complexGuard})");
|
||||
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else
|
||||
sb.AppendLine($"{i} {writer}.Instance.ScanObject(sde_{s}.Value, context, snd_{s});");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1378,6 +1506,269 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline write of a primitive/string/enum value in non-property context (no PropertySkip).
|
||||
/// Matches runtime TryWritePrimitive wire format: TinyInt for small int, type code + value otherwise.
|
||||
/// Used for dictionary key/value writes.
|
||||
/// </summary>
|
||||
private static void EmitWritePrimitiveValue(StringBuilder sb, PropertyTypeKind kind, string a, string suffix, string i)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case PropertyTypeKind.Int32:
|
||||
sb.AppendLine($"{i}if (BinaryTypeCode.TryEncodeTinyInt({a}, out var tk_{suffix})) context.WriteByte(tk_{suffix});");
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}");
|
||||
break;
|
||||
case PropertyTypeKind.Int64:
|
||||
sb.AppendLine($"{i}if ({a} >= int.MinValue && {a} <= int.MaxValue)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var iv_{suffix} = (int){a};");
|
||||
sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(iv_{suffix}, out var tk_{suffix})) context.WriteByte(tk_{suffix});");
|
||||
sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(iv_{suffix}); }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}");
|
||||
break;
|
||||
case PropertyTypeKind.Boolean:
|
||||
sb.AppendLine($"{i}context.WriteByte({a} ? BinaryTypeCode.True : BinaryTypeCode.False);");
|
||||
break;
|
||||
case PropertyTypeKind.Double:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float64); context.WriteRaw({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Single:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Float32); context.WriteRaw({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Decimal:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Decimal); context.WriteDecimalBits({a});");
|
||||
break;
|
||||
case PropertyTypeKind.DateTime:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTime); context.WriteDateTimeBits({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Guid:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Guid); context.WriteGuidBits({a});");
|
||||
break;
|
||||
case PropertyTypeKind.TimeSpan:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.TimeSpan); context.WriteRaw({a}.Ticks);");
|
||||
break;
|
||||
case PropertyTypeKind.DateTimeOffset:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.DateTimeOffset); context.WriteDateTimeOffsetBits({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Byte:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt8); context.WriteByte({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Int16:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.Int16); context.WriteRaw({a});");
|
||||
break;
|
||||
case PropertyTypeKind.UInt16:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt16); context.WriteRaw({a});");
|
||||
break;
|
||||
case PropertyTypeKind.UInt32:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt32); context.WriteVarUInt({a});");
|
||||
break;
|
||||
case PropertyTypeKind.UInt64:
|
||||
sb.AppendLine($"{i}context.WriteByte(BinaryTypeCode.UInt64); context.WriteVarULong({a});");
|
||||
break;
|
||||
case PropertyTypeKind.Enum:
|
||||
sb.AppendLine($"{i}{{ var ev_{suffix} = (int){a}; context.WriteByte(BinaryTypeCode.Enum);");
|
||||
sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(ev_{suffix}, out var te_{suffix})) context.WriteByte(te_{suffix});");
|
||||
sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(ev_{suffix}); }} }}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline dictionary write. Wire format: [Dictionary][count][key₁ value₁ key₂ value₂ ...].
|
||||
/// Keys/values are written with type codes matching runtime TryWritePrimitive/WriteValue.
|
||||
/// Eliminates GetWrapper dictionary lookup for all inlineable key/value types.
|
||||
/// </summary>
|
||||
private static void EmitDirectDictionaryWrite(StringBuilder sb, PropInfo p, string a, string i)
|
||||
{
|
||||
var s = p.Name;
|
||||
var keyType = p.DictKeyTypeName ?? "object";
|
||||
var valType = p.DictValueTypeName ?? "object";
|
||||
|
||||
if (p.IsNullable)
|
||||
{
|
||||
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}{{");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}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} {{");
|
||||
|
||||
var k = $"kvp_{s}.Key";
|
||||
var v = $"kvp_{s}.Value";
|
||||
var ii = i + " ";
|
||||
|
||||
// Write key
|
||||
if (p.DictKeyKind == PropertyTypeKind.String)
|
||||
{
|
||||
if (p.InterningFlags == 0)
|
||||
sb.AppendLine($"{ii}context.StringInternEligible = false;");
|
||||
else
|
||||
sb.AppendLine($"{ii}context.StringInternEligible = ({p.InterningFlags} & (1 << (int)context.Options.UseStringInterning)) != 0;");
|
||||
sb.AppendLine($"{ii}AcBinarySerializer.WriteStringGenerated({k}, context);");
|
||||
}
|
||||
else if (IsMarkerless(p.DictKeyKind) || p.DictKeyKind == PropertyTypeKind.Enum)
|
||||
{
|
||||
EmitWritePrimitiveValue(sb, p.DictKeyKind, k, $"dk_{s}", ii);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({k}, typeof({keyType}), context, nd_{s});");
|
||||
}
|
||||
|
||||
// Write value
|
||||
if (p.DictValueKind == PropertyTypeKind.String)
|
||||
{
|
||||
// String value: null → Null, non-null → WriteStringGenerated
|
||||
sb.AppendLine($"{ii}if ({v} == null) context.WriteByte(BinaryTypeCode.Null);");
|
||||
sb.AppendLine($"{ii}else");
|
||||
sb.AppendLine($"{ii}{{");
|
||||
if (p.InterningFlags == 0)
|
||||
sb.AppendLine($"{ii} context.StringInternEligible = false;");
|
||||
else
|
||||
sb.AppendLine($"{ii} context.StringInternEligible = ({p.InterningFlags} & (1 << (int)context.Options.UseStringInterning)) != 0;");
|
||||
sb.AppendLine($"{ii} AcBinarySerializer.WriteStringGenerated({v}, context);");
|
||||
sb.AppendLine($"{ii}}}");
|
||||
}
|
||||
else if (IsMarkerless(p.DictValueKind) || p.DictValueKind == PropertyTypeKind.Enum)
|
||||
{
|
||||
EmitWritePrimitiveValue(sb, p.DictValueKind, v, $"dv_{s}", ii);
|
||||
}
|
||||
else if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter)
|
||||
{
|
||||
EmitDictValueComplexWrite(sb, p, v, s, ii);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for non-inlineable value types
|
||||
sb.AppendLine($"{ii}AcBinarySerializer.WriteValueGenerated({v}, typeof({valType}), context, nd_{s});");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline write for a Complex+SGen dictionary value with ref tracking and metadata support.
|
||||
/// Mirrors EmitDirectCollectionWrite per-element write pattern.
|
||||
/// </summary>
|
||||
private static void EmitDictValueComplexWrite(StringBuilder sb, PropInfo p, string v, string s, string i)
|
||||
{
|
||||
var writer = p.DictValueWriterClassName!;
|
||||
var valType = p.DictValueTypeName!;
|
||||
|
||||
sb.AppendLine($"{i}if ({v} == null) {{ context.WriteByte(BinaryTypeCode.Null); }}");
|
||||
sb.AppendLine($"{i}else if (nd_{s} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); }}");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
|
||||
if (!p.DictValueNeedsRefScan)
|
||||
{
|
||||
if (!p.DictValueEnableMetadata)
|
||||
{
|
||||
// No ref, no metadata → always Object
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// No ref, metadata possible
|
||||
sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({valType})));");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dvRefGuard = p.DictValueIsIId
|
||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||
|
||||
if (!p.DictValueEnableMetadata)
|
||||
{
|
||||
// Ref tracking, no metadata
|
||||
sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full path: ref tracking + metadata
|
||||
sb.AppendLine($"{i} var isFirstMeta_dv_{s} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext<TOutput>.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({valType})));");
|
||||
sb.AppendLine($"{i} if ({dvRefGuard} && context.TryConsumeWritePlanEntry(out var dpe_{s}))");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (!dpe_{s}.IsFirst)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)dpe_{s}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.DictValueTypeNameHash, p.DictValuePropertyHashes!, $"isFirstMeta_dv_{s}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({v}, context, nd_{s});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i)
|
||||
{
|
||||
switch (k)
|
||||
|
|
@ -2623,6 +3014,24 @@ internal sealed class PropInfo
|
|||
public bool DictValueHasGeneratedWriter { get; }
|
||||
/// <summary>Generated writer class name for dictionary value type.</summary>
|
||||
public string? DictValueWriterClassName { get; }
|
||||
/// <summary>True if dictionary value type implements IId<T>.</summary>
|
||||
public bool DictValueIsIId { get; }
|
||||
/// <summary>When false, dict value type skips inline metadata.</summary>
|
||||
public bool DictValueEnableMetadata { get; }
|
||||
/// <summary>FNV-1a hash of dict value type name.</summary>
|
||||
public int DictValueTypeNameHash { get; }
|
||||
/// <summary>FNV-1a hashes of dict value type's properties.</summary>
|
||||
public int[]? DictValuePropertyHashes { get; }
|
||||
/// <summary>When true, dict value subtree has IId types needing scan.</summary>
|
||||
public bool DictValueNeedsIdScan { get; }
|
||||
/// <summary>When true, dict value subtree has non-IId ref tracking.</summary>
|
||||
public bool DictValueNeedsAllRefScan { get; }
|
||||
/// <summary>When true, dict value subtree needs string interning scan.</summary>
|
||||
public bool DictValueNeedsInternScan { get; }
|
||||
/// <summary>Derived: DictValueNeedsIdScan || DictValueNeedsAllRefScan.</summary>
|
||||
public bool DictValueNeedsRefScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan;
|
||||
/// <summary>Derived: any dict value scan axis active.</summary>
|
||||
public bool DictValueNeedsScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan || DictValueNeedsInternScan;
|
||||
|
||||
// UseMetadata inline hash-ek (Complex/Collection child típushoz)
|
||||
/// <summary>FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter.</summary>
|
||||
|
|
@ -2667,6 +3076,9 @@ internal sealed class PropInfo
|
|||
PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown, PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown,
|
||||
string? dictKeyTypeName = null, string? dictValueTypeName = null,
|
||||
bool dictValueHasGeneratedWriter = false, string? dictValueWriterClassName = null,
|
||||
bool dictValueIsIId = false, bool dictValueEnableMetadata = true,
|
||||
int dictValueTypeNameHash = 0, int[]? dictValuePropertyHashes = null,
|
||||
bool dictValueNeedsIdScan = true, bool dictValueNeedsAllRefScan = true, bool dictValueNeedsInternScan = true,
|
||||
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
|
||||
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null,
|
||||
bool childEnableMetadata = true, bool elementEnableMetadata = true,
|
||||
|
|
@ -2698,6 +3110,13 @@ internal sealed class PropInfo
|
|||
DictValueTypeName = dictValueTypeName;
|
||||
DictValueHasGeneratedWriter = dictValueHasGeneratedWriter;
|
||||
DictValueWriterClassName = dictValueWriterClassName;
|
||||
DictValueIsIId = dictValueIsIId;
|
||||
DictValueEnableMetadata = dictValueEnableMetadata;
|
||||
DictValueTypeNameHash = dictValueTypeNameHash;
|
||||
DictValuePropertyHashes = dictValuePropertyHashes;
|
||||
DictValueNeedsIdScan = dictValueNeedsIdScan;
|
||||
DictValueNeedsAllRefScan = dictValueNeedsAllRefScan;
|
||||
DictValueNeedsInternScan = dictValueNeedsInternScan;
|
||||
ChildTypeNameHash = childTypeNameHash;
|
||||
ChildPropertyHashes = childPropertyHashes;
|
||||
ElementTypeNameHash = elementTypeNameHash;
|
||||
|
|
|
|||
Loading…
Reference in New Issue