Add full UseMetadata support to SGen with inline hashes
Implements inline type metadata emission in the source generator, matching runtime TypeMetadataBase. Computes FNV-1a hashes for type and property names, stores them in generated code, and emits metadata when UseMetadata is enabled. Adds per-type slot allocation and tracking for first/repeated metadata writes. Removes runtime fallback for UseMetadata, ensuring all logic is handled inline. Updates property filtering/order to match runtime, and optimizes Int32/Int64 skip logic. Thread-safe slot allocation is used for metadata tracking.
This commit is contained in:
parent
e2269d3ecf
commit
deffb77de4
|
|
@ -80,6 +80,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
bool hasGenWriter = false;
|
||||
bool propTypeIsIId = false;
|
||||
string? writerClassName = null;
|
||||
int childTypeNameHash = 0;
|
||||
int[]? childPropertyHashes = null;
|
||||
if (kind == PropertyTypeKind.Complex)
|
||||
{
|
||||
// Resolve to the actual type symbol (strip nullable annotation for ref types)
|
||||
|
|
@ -104,6 +106,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
writerClassName = string.IsNullOrEmpty(ns)
|
||||
? $"{flatName}_GeneratedWriter"
|
||||
: $"{ns}.{flatName}_GeneratedWriter";
|
||||
|
||||
// UseMetadata: compute child type hash-es for inline metadata
|
||||
childTypeNameHash = ComputeFnvHash(resolvedType.Name);
|
||||
childPropertyHashes = ComputeChildPropertyHashes(resolvedType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +120,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
string? elemWriterClassName = null;
|
||||
string? collKind = null;
|
||||
string? elemFullTypeName = null;
|
||||
int elementTypeNameHash = 0;
|
||||
int[]? elementPropertyHashes = null;
|
||||
if (kind == PropertyTypeKind.Collection)
|
||||
{
|
||||
var elemType = GetCollectionElementType(p.Type);
|
||||
|
|
@ -161,6 +169,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
elemWriterClassName = string.IsNullOrEmpty(ens)
|
||||
? $"{elemFlatName}_GeneratedWriter"
|
||||
: $"{ens}.{elemFlatName}_GeneratedWriter";
|
||||
|
||||
// UseMetadata: compute element type hash-es for inline metadata
|
||||
elementTypeNameHash = ComputeFnvHash(resolvedElem.Name);
|
||||
elementPropertyHashes = ComputeChildPropertyHashes(resolvedElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -173,7 +185,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
kind,
|
||||
p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type),
|
||||
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName,
|
||||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, collKind, elemFullTypeName));
|
||||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, collKind, elemFullTypeName,
|
||||
childTypeNameHash, childPropertyHashes,
|
||||
elementTypeNameHash, elementPropertyHashes));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +207,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
else
|
||||
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
return new SerializableClassInfo(namespaceName, BuildFlatName(typeSymbol), typeSymbol.ToDisplayString(), properties);
|
||||
var className = BuildFlatName(typeSymbol);
|
||||
var typeNameHash = ComputeFnvHash(typeSymbol.Name);
|
||||
var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
|
||||
return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, typeNameHash, propertyNameHashes);
|
||||
}
|
||||
|
||||
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
||||
|
|
@ -225,6 +242,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedWriter : IGeneratedBinaryWriter");
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
|
||||
sb.AppendLine($" internal static readonly int s_metadataSlot = AcBinarySerializer.AllocateMetadataSlot();");
|
||||
sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};");
|
||||
sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ ");
|
||||
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(" {");
|
||||
|
|
@ -374,10 +396,10 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
|
||||
/// <summary>
|
||||
/// Emits direct object write — bypasses GetWrapper + WriteObject entirely.
|
||||
/// Writes marker bytes + calls child GeneratedWriter.WriteProperties inline.
|
||||
/// Writes marker bytes + inline metadata (UseMetadata) + calls child GeneratedWriter.WriteProperties.
|
||||
/// IId types: guard ReferenceHandling != None (tracked in OnlyId + All).
|
||||
/// Non-IId types: guard ReferenceHandling == All (tracked only in All mode).
|
||||
/// Falls back to WriteObjectGenerated when context.IsDirectObjectWrite is false (UseMetadata).
|
||||
/// No fallback to WriteObjectGenerated — handles both UseMetadata=true and false inline.
|
||||
/// </summary>
|
||||
private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i)
|
||||
{
|
||||
|
|
@ -387,24 +409,23 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
if (p.IsNullable)
|
||||
{
|
||||
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else if (context.IsDirectObjectWrite)");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}if (context.IsDirectObjectWrite)");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}{{");
|
||||
}
|
||||
|
||||
// MaxDepth check — matches WriteObjectGenerated
|
||||
sb.AppendLine($"{i} if (depth > context.MaxDepth) context.WriteByte(BinaryTypeCode.Null);");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
||||
// UseMetadata: register type for first/repeated tracking via slot (no GetWrapper needed)
|
||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||
|
||||
// Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior.
|
||||
// IId types: tracked in OnlyId + All → guard: ReferenceHandling != None
|
||||
// Non-IId types: tracked only in All → guard: ReferenceHandling == All
|
||||
// This matches UseTypeReferenceHandling: (IsIId || ReferenceHandling == All) && ReferenceHandling != None
|
||||
var refGuard = p.IsIId
|
||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||
|
|
@ -417,52 +438,74 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
|
||||
// Fallback for non-direct mode (UseMetadata=true or HasPropertyFilter=true)
|
||||
if (p.IsNullable)
|
||||
sb.AppendLine($"{i}else AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i} AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline metadata write: typeNameHash + (if first) propCount + property hashes.
|
||||
/// All values are compile-time constants.
|
||||
/// </summary>
|
||||
private static void EmitInlineMetadata(StringBuilder sb, int typeNameHash, int[] propertyHashes, string isFirstVar, string i)
|
||||
{
|
||||
sb.AppendLine($"{i}context.WriteRaw({typeNameHash});");
|
||||
sb.AppendLine($"{i}if ({isFirstVar})");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt({(uint)propertyHashes.Length});");
|
||||
foreach (var hash in propertyHashes)
|
||||
sb.AppendLine($"{i} context.WriteRaw({hash});");
|
||||
sb.AppendLine($"{i}}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits inline collection write for List<T> / T[] where T is a Complex type with generated writer.
|
||||
/// Bypasses GetWrapper + WriteArray + WriteValue per-element dispatch entirely.
|
||||
/// Wire format: [Array marker][VarUInt count][elem₁ marker+props][elem₂ marker+props]...
|
||||
/// Falls back to WriteValueGenerated when context.IsDirectObjectWrite is false.
|
||||
/// Handles both UseMetadata=true and false inline — no fallback to WriteValueGenerated.
|
||||
/// </summary>
|
||||
private static void EmitDirectCollectionWrite(StringBuilder sb, PropInfo p, string a, string i)
|
||||
{
|
||||
var writer = p.ElementWriterClassName;
|
||||
var elemType = p.ElementFullTypeName;
|
||||
|
||||
if (p.IsNullable)
|
||||
{
|
||||
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else if (context.IsDirectObjectWrite)");
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i}{{");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}if (context.IsDirectObjectWrite)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
}
|
||||
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Array);");
|
||||
|
||||
// Get count and iteration based on collection kind
|
||||
|
|
@ -477,7 +520,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
}
|
||||
else if (p.CollectionKind == "Counted")
|
||||
{
|
||||
// HashSet<T>, Queue<T>, ICollection<T>, IReadOnlyCollection<T>, etc. — Count + foreach
|
||||
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 + 1;");
|
||||
|
|
@ -494,15 +536,15 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} var elem_{p.Name} = list_{p.Name}[i_{p.Name}];");
|
||||
}
|
||||
|
||||
// Per-element write — same logic as EmitDirectObjectWrite but for each element
|
||||
// Per-element write
|
||||
var e = $"elem_{p.Name}";
|
||||
// Elements in a collection can be null (runtime writes Null marker)
|
||||
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||
sb.AppendLine($"{i} if (nextDepth_{p.Name} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||
|
||||
// Inline ref tracking: guard depends on IId vs non-IId element type to match scan pass.
|
||||
// IId elements: tracked in OnlyId + All → guard: ReferenceHandling != None
|
||||
// Non-IId elements: tracked only in All → guard: ReferenceHandling == All
|
||||
// UseMetadata: register element type for first/repeated tracking
|
||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||
|
||||
// Inline ref tracking
|
||||
var elemRefGuard = p.ElementIsIId
|
||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||
|
|
@ -515,28 +557,36 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} {{");
|
||||
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i} else");
|
||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||
sb.AppendLine($"{i} }}");
|
||||
|
||||
sb.AppendLine($"{i} }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
|
||||
// Fallback for non-direct mode
|
||||
if (p.IsNullable)
|
||||
sb.AppendLine($"{i}else AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"{i}else");
|
||||
sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitSkip(StringBuilder sb, PropertyTypeKind k, string a, string typeName, string i)
|
||||
|
|
@ -544,13 +594,28 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
switch (k)
|
||||
{
|
||||
case PropertyTypeKind.Int32:
|
||||
{
|
||||
// Mirrors runtime WritePropertyOrSkip → WriteInt32 (TinyInt optimization)
|
||||
var s32 = a.Replace(".", "_");
|
||||
sb.AppendLine($"{i}if ({a} == 0) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else if (BinaryTypeCode.TryEncodeTinyInt({a}, out var ti_{s32})) context.WriteByte(ti_{s32});");
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt({a}); }}");
|
||||
break;
|
||||
}
|
||||
case PropertyTypeKind.Int64:
|
||||
{
|
||||
// Mirrors runtime WritePropertyOrSkip → WriteInt64 → WriteInt32 (int range + TinyInt)
|
||||
var s64 = a.Replace(".", "_");
|
||||
sb.AppendLine($"{i}if ({a} == 0L) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else if ({a} >= int.MinValue && {a} <= int.MaxValue)");
|
||||
sb.AppendLine($"{i}{{");
|
||||
sb.AppendLine($"{i} var iv_{s64} = (int){a};");
|
||||
sb.AppendLine($"{i} if (BinaryTypeCode.TryEncodeTinyInt(iv_{s64}, out var ti_{s64})) context.WriteByte(ti_{s64});");
|
||||
sb.AppendLine($"{i} else {{ context.WriteByte(BinaryTypeCode.Int32); context.WriteVarInt(iv_{s64}); }}");
|
||||
sb.AppendLine($"{i}}}");
|
||||
sb.AppendLine($"{i}else {{ context.WriteByte(BinaryTypeCode.Int64); context.WriteVarLong({a}); }}");
|
||||
break;
|
||||
}
|
||||
case PropertyTypeKind.Boolean:
|
||||
sb.AppendLine($"{i}if (!{a}) context.WriteByte(BinaryTypeCode.PropertySkip);");
|
||||
sb.AppendLine($"{i}else context.WriteByte(BinaryTypeCode.True);");
|
||||
|
|
@ -675,6 +740,64 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
return string.Join("_", parts);
|
||||
}
|
||||
|
||||
#region FNV-1a Hash (compile-time)
|
||||
|
||||
private static int ComputeFnvHash(string value)
|
||||
{
|
||||
uint hash = 2166136261;
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
hash ^= value[i];
|
||||
hash *= 16777619;
|
||||
}
|
||||
return (int)hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes FNV-1a hashes for all serializable properties of a child type.
|
||||
/// Property filtering and ordering matches runtime TypeMetadataBase exactly:
|
||||
/// public get+set, non-indexer, non-static, no ignore attributes, sorted (Id first if IId, then alphabetical).
|
||||
/// </summary>
|
||||
private static int[] ComputeChildPropertyHashes(ITypeSymbol resolvedType)
|
||||
{
|
||||
var propNames = new List<string>();
|
||||
foreach (var member in resolvedType.GetMembers())
|
||||
{
|
||||
if (member is IPropertySymbol cp &&
|
||||
cp.DeclaredAccessibility == Accessibility.Public &&
|
||||
cp.GetMethod != null && cp.SetMethod != null &&
|
||||
!cp.IsIndexer && !cp.IsStatic)
|
||||
{
|
||||
var hasIgnore = cp.GetAttributes().Any(a =>
|
||||
{
|
||||
var name = a.AttributeClass?.Name ?? "";
|
||||
return name == "JsonIgnoreAttribute" || name == "IgnoreMemberAttribute" || name == "BsonIgnoreAttribute";
|
||||
});
|
||||
if (hasIgnore) continue;
|
||||
propNames.Add(cp.Name);
|
||||
}
|
||||
}
|
||||
|
||||
var childIsIId = resolvedType.AllInterfaces.Any(ifc =>
|
||||
ifc.IsGenericType &&
|
||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||
|
||||
if (childIsIId)
|
||||
propNames.Sort((a, b) =>
|
||||
{
|
||||
var ai = a == "Id" ? 0 : 1;
|
||||
var bi = b == "Id" ? 0 : 1;
|
||||
if (ai != bi) return ai.CompareTo(bi);
|
||||
return string.Compare(a, b, StringComparison.Ordinal);
|
||||
});
|
||||
else
|
||||
propNames.Sort(StringComparer.Ordinal);
|
||||
|
||||
return propNames.Select(ComputeFnvHash).ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type analysis
|
||||
|
||||
private static bool IsNullableVT(ITypeSymbol t) =>
|
||||
|
|
@ -765,8 +888,12 @@ internal sealed class SerializableClassInfo
|
|||
public string ClassName { get; }
|
||||
public string FullTypeName { get; }
|
||||
public List<PropInfo> Properties { get; }
|
||||
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p)
|
||||
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; }
|
||||
/// <summary>FNV-1a hash of ClassName (matches runtime SourceType.Name hash)</summary>
|
||||
public int TypeNameHash { get; }
|
||||
/// <summary>FNV-1a hash of each property name, in property order</summary>
|
||||
public int[] PropertyNameHashes { get; }
|
||||
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, int typeNameHash, int[] propertyNameHashes)
|
||||
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; }
|
||||
}
|
||||
|
||||
internal sealed class PropInfo
|
||||
|
|
@ -808,10 +935,22 @@ internal sealed class PropInfo
|
|||
/// <summary>Full element type name for generated code (e.g. "SharedTag").</summary>
|
||||
public string? ElementFullTypeName { get; }
|
||||
|
||||
// UseMetadata inline hash-ek (Complex/Collection child típushoz)
|
||||
/// <summary>FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter.</summary>
|
||||
public int ChildTypeNameHash { get; }
|
||||
/// <summary>FNV-1a hashes of child type's properties. Only set when HasGeneratedWriter.</summary>
|
||||
public int[]? ChildPropertyHashes { get; }
|
||||
/// <summary>FNV-1a hash of collection element type name. Only set when ElementHasGeneratedWriter.</summary>
|
||||
public int ElementTypeNameHash { get; }
|
||||
/// <summary>FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter.</summary>
|
||||
public int[]? ElementPropertyHashes { get; }
|
||||
|
||||
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
|
||||
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null,
|
||||
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
|
||||
string? elementWriterClassName = null, string? collectionKind = null, string? elementFullTypeName = null)
|
||||
string? elementWriterClassName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
||||
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
|
||||
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null)
|
||||
{
|
||||
Name = n;
|
||||
TypeName = tn;
|
||||
|
|
@ -827,6 +966,10 @@ internal sealed class PropInfo
|
|||
ElementWriterClassName = elementWriterClassName;
|
||||
CollectionKind = collectionKind;
|
||||
ElementFullTypeName = elementFullTypeName;
|
||||
ChildTypeNameHash = childTypeNameHash;
|
||||
ChildPropertyHashes = childPropertyHashes;
|
||||
ElementTypeNameHash = elementTypeNameHash;
|
||||
ElementPropertyHashes = elementPropertyHashes;
|
||||
// Mirror runtime _interningFlags computation from BinaryPropertyAccessorBase
|
||||
int flags = 0;
|
||||
if (stringInternAttr == true) flags |= (1 << 1); // Attribute bit
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ public static partial class AcBinarySerializer
|
|||
#endregion
|
||||
|
||||
private IdentityMap<string, InternEntry>? _stringInternMap;
|
||||
private readonly ulong[] _metadataSeenBits = new ulong[4]; // 256 SGen slot (bit per type, first/repeated tracking)
|
||||
private HashSet<int>? _metadataSeenOverflow; // fallback for slot >= 256
|
||||
private int _nextCacheIndex; // Next dense cache index to assign (starts at 0, uses ++_nextCacheIndex)
|
||||
public int NextFirstIndex; // Next first occurrence index for scan pass. Direct access for performance.
|
||||
|
||||
|
|
@ -277,6 +279,8 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
|
||||
_stringInternMap?.Reset();
|
||||
_metadataSeenBits.AsSpan().Clear();
|
||||
_metadataSeenOverflow?.Clear();
|
||||
_nextCacheIndex = 0;
|
||||
NextFirstIndex = 0;
|
||||
ScanVisitIndex = 0;
|
||||
|
|
@ -782,6 +786,26 @@ public static partial class AcBinarySerializer
|
|||
return true; // első előfordulás
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SGen inline metadata regisztráció — slot-alapú, nem kell TypeMetadataWrapper.
|
||||
/// Minden SGen típus kap egy compile-time slot indexet (AllocateMetadataSlot).
|
||||
/// Első 256 slot: bit művelet (ulong[4]), felette: HashSet fallback.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RegisterMetadataTypeBySlot(int slot)
|
||||
{
|
||||
if (slot < 256)
|
||||
{
|
||||
ref var bits = ref _metadataSeenBits[slot >> 6];
|
||||
var mask = 1UL << (slot & 63);
|
||||
if ((bits & mask) != 0) return false;
|
||||
bits |= mask;
|
||||
return true;
|
||||
}
|
||||
_metadataSeenOverflow ??= new HashSet<int>();
|
||||
return _metadataSeenOverflow.Add(slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline metadata kiírása az ObjectWithMetadata marker után.
|
||||
/// Első előfordulás: [propNameHash (4b)][propCount (VarUInt)][hash0 (4b)][hash1 (4b)]...
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using AyCode.Core.Serializers.Expressions;
|
|||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
|
@ -250,6 +251,19 @@ public static partial class AcBinarySerializer
|
|||
GeneratedWriterRegistry.Register(type, writer);
|
||||
}
|
||||
|
||||
#region UseMetadata Slot Allocation
|
||||
|
||||
private static int s_nextMetadataSlot;
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a unique slot index for SGen UseMetadata first/repeated tracking.
|
||||
/// Called once per SGen type at startup (ModuleInitializer). Thread-safe.
|
||||
/// Slot is used by BinarySerializationContext.RegisterMetadataTypeBySlot.
|
||||
/// </summary>
|
||||
internal static int AllocateMetadataSlot() => Interlocked.Increment(ref s_nextMetadataSlot) - 1;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe registry for generated writers. Looked up once per TypeMetadataWrapper creation.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Reference in New Issue