Improve collection handling in generator; enable all test models
- Refined collection kind mapping: IList<T>/IReadOnlyList<T> now treated as "IndexedCollection" for codegen, distinct from List<T> - Generated code uses CollectionsMarshal.AsSpan for List<T> iteration, improving performance - Updated generated read/write logic for collections to match new distinctions - Added System.Runtime.InteropServices to generated code for span support - Increased test iteration count in Program.cs for more robust benchmarks - Enabled source-generated binary serialization for all test models by setting [AcBinarySerializable(true)]
This commit is contained in:
parent
5ebcd03e87
commit
e6afd21fef
|
|
@ -50,7 +50,7 @@ public static class Program
|
|||
private static int TestIterations = 1;
|
||||
#else
|
||||
private static int WarmupIterations = 5000;
|
||||
private static int TestIterations = 1000;
|
||||
private static int TestIterations = 5000;
|
||||
|
||||
//private static int WarmupIterations = 5000;
|
||||
//private static int TestIterations = 2000;
|
||||
|
|
|
|||
|
|
@ -193,8 +193,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
collKind = origDef switch
|
||||
{
|
||||
"System.Collections.Generic.List<T>" => "List",
|
||||
"System.Collections.Generic.IList<T>" => "List", // has Count + indexer
|
||||
"System.Collections.Generic.IReadOnlyList<T>" => "List", // has Count + indexer
|
||||
"System.Collections.Generic.IList<T>" => "IndexedCollection",
|
||||
"System.Collections.Generic.IReadOnlyList<T>" => "IndexedCollection",
|
||||
"System.Collections.Generic.HashSet<T>" => "Counted", // has Count, no indexer
|
||||
"System.Collections.Generic.Queue<T>" => "Counted",
|
||||
"System.Collections.Generic.ICollection<T>" => "Counted",
|
||||
|
|
@ -304,6 +304,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine("// <auto-generated/>");
|
||||
sb.AppendLine("#nullable enable");
|
||||
sb.AppendLine("using System.Runtime.CompilerServices;");
|
||||
sb.AppendLine("using System.Runtime.InteropServices;");
|
||||
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
|
||||
// ReferenceHandlingMode is needed for ScanObject self ref tracking and direct object write/scan
|
||||
sb.AppendLine("using AyCode.Core.Serializers;");
|
||||
|
|
@ -753,6 +754,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
||||
}
|
||||
else if (p.CollectionKind == "List")
|
||||
{
|
||||
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan(scol_{p.Name});");
|
||||
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < span_{p.Name}.Length; si_{p.Name}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var se_{p.Name} = span_{p.Name}[si_{p.Name}];");
|
||||
}
|
||||
else if (p.CollectionKind == "IndexedCollection")
|
||||
{
|
||||
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Count; si_{p.Name}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
|
@ -812,6 +820,13 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} var se_{p.Name} = scol_{p.Name}[si_{p.Name}];");
|
||||
}
|
||||
else if (p.CollectionKind == "List")
|
||||
{
|
||||
sb.AppendLine($"{i} var span_{p.Name} = CollectionsMarshal.AsSpan(scol_{p.Name});");
|
||||
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < span_{p.Name}.Length; si_{p.Name}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
sb.AppendLine($"{i} var se_{p.Name} = span_{p.Name}[si_{p.Name}];");
|
||||
}
|
||||
else if (p.CollectionKind == "IndexedCollection")
|
||||
{
|
||||
sb.AppendLine($"{i} for (var si_{p.Name} = 0; si_{p.Name} < scol_{p.Name}.Count; si_{p.Name}++)");
|
||||
sb.AppendLine($"{i} {{");
|
||||
|
|
@ -1025,14 +1040,23 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})");
|
||||
sb.AppendLine($"{i} {{");
|
||||
}
|
||||
else // List, IList<T>, IReadOnlyList<T> — Count + indexer
|
||||
else if (p.CollectionKind == "IndexedCollection")
|
||||
{
|
||||
sb.AppendLine($"{i} var list_{p.Name} = {a};");
|
||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)list_{p.Name}.Count);");
|
||||
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;");
|
||||
sb.AppendLine($"{i} for (var i_{p.Name} = 0; i_{p.Name} < list_{p.Name}.Count; i_{p.Name}++)");
|
||||
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} = list_{p.Name}[i_{p.Name}];");
|
||||
sb.AppendLine($"{i} var elem_{p.Name} = col_{p.Name}[i_{p.Name}];");
|
||||
}
|
||||
else // List — CollectionsMarshal.AsSpan for zero-overhead iteration
|
||||
{
|
||||
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 + 1;");
|
||||
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}];");
|
||||
}
|
||||
|
||||
// Per-element write
|
||||
|
|
@ -1542,7 +1566,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
EmitReadCollectionElement(sb, reader, elemCast, $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan);
|
||||
sb.AppendLine($"{i} }}");
|
||||
}
|
||||
else // List, Counted — all use List<T> with Add
|
||||
else // List, IndexedCollection, Counted — all use List<T> with Add
|
||||
{
|
||||
sb.AppendLine($"{i} var col_{s} = new System.Collections.Generic.List<{elemType}>(cnt_{s});");
|
||||
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
|
||||
|
|
@ -2042,7 +2066,7 @@ internal sealed class PropInfo
|
|||
public string? ElementWriterClassName { get; }
|
||||
/// <summary>Id type name for collection element IId types. Null if not IId.</summary>
|
||||
public string? ElementIdTypeName { get; }
|
||||
/// <summary>Collection type: "List", "Array", or null (unknown — fallback to runtime).</summary>
|
||||
/// <summary>Collection type: "List", "Array", "IndexedCollection", "Counted", or null (unknown — fallback to runtime).</summary>
|
||||
public string? CollectionKind { get; }
|
||||
/// <summary>Full element type name for generated code (e.g. "SharedTag").</summary>
|
||||
public string? ElementFullTypeName { get; }
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public enum TestUserRole
|
|||
/// Implements IId<int> for semantic $id/$ref serialization.
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class SharedTag : IId<int>
|
||||
{
|
||||
|
|
@ -80,7 +80,7 @@ public partial class SharedTag : IId<int>
|
|||
/// Shared category - for hierarchical cross-reference testing.
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class SharedCategory : IId<int>
|
||||
{
|
||||
|
|
@ -106,7 +106,7 @@ public partial class SharedCategory : IId<int>
|
|||
/// Shared user reference - appears in many places to test $ref deduplication.
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class SharedUser : IId<int>
|
||||
{
|
||||
|
|
@ -136,7 +136,7 @@ public partial class SharedUser : IId<int>
|
|||
/// User preferences - non-IId nested object
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class UserPreferences
|
||||
{
|
||||
|
|
@ -162,7 +162,7 @@ public partial class UserPreferences
|
|||
/// Does NOT implement IId, so uses standard Newtonsoft reference tracking.
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class MetadataInfo
|
||||
{
|
||||
|
|
@ -190,7 +190,7 @@ public partial class MetadataInfo
|
|||
/// Level 1: Main order - root of the hierarchy
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class TestOrder : IId<int>
|
||||
{
|
||||
|
|
@ -250,7 +250,7 @@ public partial class TestOrder : IId<int>
|
|||
/// Level 2: Order item with pallets
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class TestOrderItem : IId<int>
|
||||
{
|
||||
|
|
@ -290,7 +290,7 @@ public partial class TestOrderItem : IId<int>
|
|||
/// Level 3: Pallet containing measurements
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class TestPallet : IId<int>
|
||||
{
|
||||
|
|
@ -333,7 +333,7 @@ public partial class TestPallet : IId<int>
|
|||
/// Level 4: Measurement with multiple points
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class TestMeasurement : IId<int>
|
||||
{
|
||||
|
|
@ -368,7 +368,7 @@ public partial class TestMeasurement : IId<int>
|
|||
/// Level 5: Deepest level - measurement point
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
[MessagePackObject]
|
||||
public partial class TestMeasurementPoint : IId<int>
|
||||
{
|
||||
|
|
@ -402,7 +402,7 @@ public partial class TestMeasurementPoint : IId<int>
|
|||
/// <summary>
|
||||
/// Order with Guid Id - for testing Guid-based IId
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestGuidOrder : IId<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
|
@ -414,7 +414,7 @@ public class TestGuidOrder : IId<Guid>
|
|||
/// <summary>
|
||||
/// Item with Guid Id
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestGuidItem : IId<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
|
@ -430,7 +430,7 @@ public class TestGuidItem : IId<Guid>
|
|||
/// Simulates NopCommerce GenericAttribute - stores key-value pairs where DateTime values
|
||||
/// are stored as strings in the database.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestGenericAttribute
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -442,7 +442,7 @@ public class TestGenericAttribute
|
|||
/// DTO with GenericAttributes collection - simulates OrderDto with string-stored DateTime values.
|
||||
/// This reproduces the production bug where Binary serialization was thought to corrupt DateTime strings.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestDtoWithGenericAttributes : IId<int>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -453,7 +453,7 @@ public class TestDtoWithGenericAttributes : IId<int>
|
|||
/// <summary>
|
||||
/// Order with nullable collections for null vs empty testing
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class TestOrderWithNullableCollections
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -466,7 +466,7 @@ public class TestOrderWithNullableCollections
|
|||
/// Class with all primitive types for WASM/serialization testing
|
||||
/// </summary>
|
||||
[MemoryPackable]
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public partial class PrimitiveTestClass
|
||||
{
|
||||
public int IntValue { get; set; }
|
||||
|
|
@ -489,7 +489,7 @@ public partial class PrimitiveTestClass
|
|||
/// Class with extended primitive types for full serializer coverage.
|
||||
/// Includes DateTimeOffset, TimeSpan, Dictionary, null properties.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ExtendedPrimitiveTestClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -519,7 +519,7 @@ public class ExtendedPrimitiveTestClass
|
|||
/// <summary>
|
||||
/// Class with array of objects containing null items for WriteNull coverage
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ObjectWithNullItems
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -534,7 +534,7 @@ public class ObjectWithNullItems
|
|||
/// "Server-side" DTO with extra properties that the "client" doesn't know about.
|
||||
/// Used to test SkipValue functionality when deserializing unknown properties.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ServerCustomerDto : IId<int>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -567,7 +567,7 @@ public class ServerCustomerDto : IId<int>
|
|||
/// the deserializer must skip unknown properties correctly
|
||||
/// while still maintaining string intern table consistency.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ClientCustomerDto : IId<int>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -581,7 +581,7 @@ public class ClientCustomerDto : IId<int>
|
|||
/// Server DTO with nested objects that client doesn't know about.
|
||||
/// Tests skipping complex nested structures.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ServerOrderWithExtras : IId<int>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -602,7 +602,7 @@ public class ServerOrderWithExtras : IId<int>
|
|||
/// <summary>
|
||||
/// Client version of the order - doesn't have Customer/RelatedCustomers properties.
|
||||
/// </summary>
|
||||
[AcBinarySerializable(false)]
|
||||
[AcBinarySerializable(true)]
|
||||
public class ClientOrderSimple : IId<int>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue