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:
Loretta 2026-02-28 15:06:17 +01:00
parent 2aa2eecccd
commit 7902922195
1 changed files with 436 additions and 17 deletions

View File

@ -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&lt;T&gt;.</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;