Refactor serializer options, string fast paths & analysis
- Refactor all serializer options to use properties returning new instances (no shared mutable state); update all usages accordingly - Extract AcSerializerOptions, BinaryTypeCode, and BinaryPropertyFilterContext to dedicated files for clarity and reuse - Add DEBUG-only string interning analysis/reporting tools to AcBinarySerializer - Improve AcBinarySerializer string property serialization with direct typed getter and SIMD-optimized ASCII path - Increase benchmark/test warmup iterations and add JIT warmup delays for more reliable performance measurements - Remove redundant usings and update documentation/comments throughout - No breaking API changes, but static readonly options fields are now properties
This commit is contained in:
parent
145cc0a493
commit
1a77ee4bf9
|
|
@ -177,7 +177,7 @@ namespace AyCode.Benchmark
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
var withRefOptions = new AcBinarySerializerOptions();
|
var withRefOptions = new AcBinarySerializerOptions();
|
||||||
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
var msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
var msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
// Warm up
|
// Warm up
|
||||||
|
|
@ -364,7 +364,7 @@ namespace AyCode.Benchmark
|
||||||
Console.WriteLine($"Created order with {order.Items.Count} items");
|
Console.WriteLine($"Created order with {order.Items.Count} items");
|
||||||
|
|
||||||
Console.WriteLine("\nTesting JSON serialization...");
|
Console.WriteLine("\nTesting JSON serialization...");
|
||||||
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling;
|
||||||
var json = AcJsonSerializer.Serialize(order, jsonOptions);
|
var json = AcJsonSerializer.Serialize(order, jsonOptions);
|
||||||
|
|
||||||
// Log a quick summary to Out folder for convenience
|
// Log a quick summary to Out folder for convenience
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ using MongoDB.Bson.Serialization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
|
|
||||||
namespace AyCode.Core.Benchmarks;
|
namespace AyCode.Core.Benchmarks;
|
||||||
|
|
||||||
|
|
@ -59,23 +60,23 @@ public class SimpleBinaryBenchmark
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_testData = TestDataFactory.CreatePrimitiveTestData();
|
_testData = TestDataFactory.CreatePrimitiveTestData();
|
||||||
_binaryData = AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling());
|
_binaryData = AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling);
|
||||||
_jsonData = AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
_jsonData = AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling);
|
||||||
|
|
||||||
Console.WriteLine($"Binary: {_binaryData.Length} bytes, JSON: {_jsonData.Length} chars");
|
Console.WriteLine($"Binary: {_binaryData.Length} bytes, JSON: {_jsonData.Length} chars");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark(Description = "Binary Serialize")]
|
[Benchmark(Description = "Binary Serialize")]
|
||||||
public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling());
|
public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling);
|
||||||
|
|
||||||
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
||||||
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling);
|
||||||
|
|
||||||
[Benchmark(Description = "Binary Deserialize")]
|
[Benchmark(Description = "Binary Deserialize")]
|
||||||
public PrimitiveTestClass? DeserializeBinary() => AcBinaryDeserializer.Deserialize<PrimitiveTestClass>(_binaryData);
|
public PrimitiveTestClass? DeserializeBinary() => AcBinaryDeserializer.Deserialize<PrimitiveTestClass>(_binaryData);
|
||||||
|
|
||||||
[Benchmark(Description = "JSON Deserialize")]
|
[Benchmark(Description = "JSON Deserialize")]
|
||||||
public PrimitiveTestClass? DeserializeJson() => AcJsonDeserializer.Deserialize<PrimitiveTestClass>(_jsonData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
public PrimitiveTestClass? DeserializeJson() => AcJsonDeserializer.Deserialize<PrimitiveTestClass>(_jsonData, AcJsonSerializerOptions.WithoutReferenceHandling);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -105,8 +106,8 @@ public class ComplexBinaryBenchmark
|
||||||
pointsPerMeasurement: 3);
|
pointsPerMeasurement: 3);
|
||||||
Console.WriteLine($"Created order with {_testOrder.Items.Count} items");
|
Console.WriteLine($"Created order with {_testOrder.Items.Count} items");
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling;
|
||||||
|
|
||||||
Console.WriteLine("Serializing AcBinary...");
|
Console.WriteLine("Serializing AcBinary...");
|
||||||
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||||
|
|
@ -163,9 +164,9 @@ public class MessagePackComparisonBenchmark
|
||||||
measurementsPerPallet: 2,
|
measurementsPerPallet: 2,
|
||||||
pointsPerMeasurement: 3);
|
pointsPerMeasurement: 3);
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling;
|
||||||
|
|
||||||
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||||
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
||||||
|
|
@ -276,7 +277,7 @@ public class AcBinaryVsMessagePackFullBenchmark
|
||||||
|
|
||||||
// Setup options - WithRef uses Default (which has reference handling), NoRef explicitly disables it
|
// Setup options - WithRef uses Default (which has reference handling), NoRef explicitly disables it
|
||||||
_withRefOptions = AcBinarySerializerOptions.Default;
|
_withRefOptions = AcBinarySerializerOptions.Default;
|
||||||
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
// Serialize with different options
|
// Serialize with different options
|
||||||
|
|
@ -423,8 +424,8 @@ public class SizeComparisonBenchmark
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
_withRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_withRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
|
|
||||||
// Small order
|
// Small order
|
||||||
TestDataFactory.ResetIdCounter();
|
TestDataFactory.ResetIdCounter();
|
||||||
|
|
@ -530,7 +531,7 @@ public abstract class AcBinaryOptionsBenchmarkBase
|
||||||
private static AcBinarySerializerOptions CreateBinaryOptions(BinaryBenchmarkMode mode) => mode switch
|
private static AcBinarySerializerOptions CreateBinaryOptions(BinaryBenchmarkMode mode) => mode switch
|
||||||
{
|
{
|
||||||
BinaryBenchmarkMode.Default => new AcBinarySerializerOptions(),
|
BinaryBenchmarkMode.Default => new AcBinarySerializerOptions(),
|
||||||
BinaryBenchmarkMode.NoReferenceHandling => AcBinarySerializerOptions.WithoutReferenceHandling(),
|
BinaryBenchmarkMode.NoReferenceHandling => AcBinarySerializerOptions.WithoutReferenceHandling,
|
||||||
BinaryBenchmarkMode.FastMode => new AcBinarySerializerOptions
|
BinaryBenchmarkMode.FastMode => new AcBinarySerializerOptions
|
||||||
{
|
{
|
||||||
UseMetadata = false,
|
UseMetadata = false,
|
||||||
|
|
@ -608,7 +609,7 @@ public class LargeScaleBinaryBenchmark
|
||||||
_testOrder = TestDataFactory.CreateLargeScaleBenchmarkOrder(rootItems, pallets, measurements, points);
|
_testOrder = TestDataFactory.CreateLargeScaleBenchmarkOrder(rootItems, pallets, measurements, points);
|
||||||
Console.WriteLine($"Created order with {_testOrder.Items.Count} root items");
|
Console.WriteLine($"Created order with {_testOrder.Items.Count} root items");
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
Console.WriteLine("Serializing AcBinary...");
|
Console.WriteLine("Serializing AcBinary...");
|
||||||
|
|
@ -677,7 +678,7 @@ public class AcJsonVsSystemTextJsonBenchmark
|
||||||
_testData = TestDataFactory.CreatePrimitiveTestData();
|
_testData = TestDataFactory.CreatePrimitiveTestData();
|
||||||
|
|
||||||
// Setup options
|
// Setup options
|
||||||
_acJsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
_acJsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling;
|
||||||
_stjOptions = new JsonSerializerOptions
|
_stjOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = false,
|
WriteIndented = false,
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public class PureContractlessBenchmark
|
||||||
Status = "Available"
|
Status = "Available"
|
||||||
};
|
};
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
_acBinaryData = AcBinarySerializer.Serialize(_testData, _binaryOptions);
|
_acBinaryData = AcBinarySerializer.Serialize(_testData, _binaryOptions);
|
||||||
|
|
@ -148,7 +148,7 @@ public class SourceGeneratorVsRuntimeBenchmark
|
||||||
Status = "Available"
|
Status = "Available"
|
||||||
};
|
};
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
|
|
||||||
// MessagePack with Source Generator (uses [MessagePackObject] + [Key] attributes)
|
// MessagePack with Source Generator (uses [MessagePackObject] + [Key] attributes)
|
||||||
_msgPackOptions = MessagePackSerializerOptions.Standard;
|
_msgPackOptions = MessagePackSerializerOptions.Standard;
|
||||||
|
|
@ -254,7 +254,7 @@ public class RepeatedStringBenchmark
|
||||||
Type = i % 4 == 0 ? "TypeA" : i % 4 == 1 ? "TypeB" : i % 4 == 2 ? "TypeC" : "TypeD"
|
Type = i % 4 == 0 ? "TypeA" : i % 4 == 1 ? "TypeB" : i % 4 == 2 ? "TypeC" : "TypeD"
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
_binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
_msgPackOptions = MessagePackSerializerOptions.Standard;
|
_msgPackOptions = MessagePackSerializerOptions.Standard;
|
||||||
_msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
_msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public static class Program
|
||||||
|
|
||||||
private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
|
private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
|
||||||
|
|
||||||
private static int WarmupIterations = 5;
|
private static int WarmupIterations = 2000;
|
||||||
private static int TestIterations = 1000;
|
private static int TestIterations = 1000;
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
|
|
@ -90,8 +90,6 @@ public static class Program
|
||||||
// Print grouped results
|
// Print grouped results
|
||||||
PrintGroupedResults(allResults, testDataSets);
|
PrintGroupedResults(allResults, testDataSets);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Save results to file
|
// Save results to file
|
||||||
SaveResults(allResults, testDataSets);
|
SaveResults(allResults, testDataSets);
|
||||||
|
|
||||||
|
|
@ -122,7 +120,8 @@ public static class Program
|
||||||
sharedTag: sharedTag,
|
sharedTag: sharedTag,
|
||||||
sharedUser: sharedUser);
|
sharedUser: sharedUser);
|
||||||
|
|
||||||
var options = AcBinarySerializerOptions.WithoutReferenceHandling();
|
var options = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
|
options.UseStringInterning = false;
|
||||||
|
|
||||||
// Warmup (fills caches)
|
// Warmup (fills caches)
|
||||||
System.Console.WriteLine("Warming up (10 iterations)...");
|
System.Console.WriteLine("Warming up (10 iterations)...");
|
||||||
|
|
@ -132,6 +131,16 @@ public static class Program
|
||||||
}
|
}
|
||||||
System.Console.WriteLine("Warmup complete. Caches are now populated.");
|
System.Console.WriteLine("Warmup complete. Caches are now populated.");
|
||||||
System.Console.WriteLine();
|
System.Console.WriteLine();
|
||||||
|
|
||||||
|
// HOT PATH - this is what the profiler should capture!
|
||||||
|
System.Console.WriteLine("Running hot path (1000 iterations for profiling)...");
|
||||||
|
for (var i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
_ = AcBinarySerializer.Serialize(order, options);
|
||||||
|
}
|
||||||
|
System.Console.WriteLine("Hot path complete.");
|
||||||
|
System.Console.WriteLine();
|
||||||
|
|
||||||
System.Console.WriteLine(">>> ATTACH MEMORY PROFILER NOW <<<");
|
System.Console.WriteLine(">>> ATTACH MEMORY PROFILER NOW <<<");
|
||||||
System.Console.WriteLine("Press any key to exit...");
|
System.Console.WriteLine("Press any key to exit...");
|
||||||
System.Console.ReadKey(intercept: true);
|
System.Console.ReadKey(intercept: true);
|
||||||
|
|
@ -369,6 +378,9 @@ public static class Program
|
||||||
serializer.Warmup(WarmupIterations);
|
serializer.Warmup(WarmupIterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for tiered JIT background compilation to complete
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
// Run benchmarks
|
// Run benchmarks
|
||||||
System.Console.WriteLine($"Running benchmarks ({TestIterations} iterations)...\n");
|
System.Console.WriteLine($"Running benchmarks ({TestIterations} iterations)...\n");
|
||||||
|
|
||||||
|
|
@ -402,9 +414,10 @@ public static class Program
|
||||||
{
|
{
|
||||||
return new List<ISerializerBenchmark>
|
return new List<ISerializerBenchmark>
|
||||||
{
|
{
|
||||||
|
|
||||||
// AcBinary variants
|
// AcBinary variants
|
||||||
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault),
|
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault),
|
||||||
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling(), SerializerAcBinaryNoRef),
|
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef),
|
||||||
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode),
|
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode),
|
||||||
new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = false }, SerializerAcBinaryNoIntern),
|
new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = false }, SerializerAcBinaryNoIntern),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using static AyCode.Core.Tests.TestModels.AcSerializerModels;
|
using static AyCode.Core.Tests.TestModels.AcSerializerModels;
|
||||||
|
|
||||||
namespace AyCode.Core.Tests.Serialization;
|
namespace AyCode.Core.Tests.Serialization;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ public class GeneratedSerializerIntegrationTests
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serialize and deserialize using the regular path
|
// Serialize and deserialize using the regular path
|
||||||
var bytes = AcBinarySerializer.Serialize(original, AcBinarySerializerOptions.WithoutReferenceHandling());
|
var bytes = AcBinarySerializer.Serialize(original, AcBinarySerializerOptions.WithoutReferenceHandling);
|
||||||
var deserialized = AcBinaryDeserializer.Deserialize<GeneratedSerializerTestModel>(bytes);
|
var deserialized = AcBinaryDeserializer.Deserialize<GeneratedSerializerTestModel>(bytes);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using AyCode.Core.Serializers.Attributes;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
|
|
@ -12,7 +13,7 @@ namespace AyCode.Core.Tests.Serialization;
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class QuickBenchmark
|
public class QuickBenchmark
|
||||||
{
|
{
|
||||||
private static readonly MessagePackSerializerOptions MsgPackOptions =
|
private static readonly MessagePackSerializerOptions MsgPackOptions =
|
||||||
ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
private const int DefaultIterations = 1000;
|
private const int DefaultIterations = 1000;
|
||||||
|
|
@ -129,7 +130,7 @@ public class QuickBenchmark
|
||||||
var deserializeMs = sw.Elapsed.TotalMilliseconds;
|
var deserializeMs = sw.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
// JSON comparison
|
// JSON comparison
|
||||||
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling;
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
string json = null!;
|
string json = null!;
|
||||||
for (int i = 0; i < iterations; i++)
|
for (int i = 0; i < iterations; i++)
|
||||||
|
|
@ -184,7 +185,7 @@ public class QuickBenchmark
|
||||||
}
|
}
|
||||||
|
|
||||||
const int iterations = DefaultIterations;
|
const int iterations = DefaultIterations;
|
||||||
|
|
||||||
// With interning (default)
|
// With interning (default)
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
byte[] withInterning = null!;
|
byte[] withInterning = null!;
|
||||||
|
|
@ -290,7 +291,7 @@ public class QuickBenchmark
|
||||||
Console.WriteLine($"{"Deserialize (ms)",-25} {acBinaryDeserMs,12:F2} {msgPackDeserMs,12:F2} {acBinaryDeserMs / msgPackDeserMs,9:F2}x");
|
Console.WriteLine($"{"Deserialize (ms)",-25} {acBinaryDeserMs,12:F2} {msgPackDeserMs,12:F2} {acBinaryDeserMs / msgPackDeserMs,9:F2}x");
|
||||||
Console.WriteLine($"{"Round-trip (ms)",-25} {acBinarySerMs + acBinaryDeserMs,12:F2} {msgPackSerMs + msgPackDeserMs,12:F2} {(acBinarySerMs + acBinaryDeserMs) / (msgPackSerMs + msgPackDeserMs),9:F2}x");
|
Console.WriteLine($"{"Round-trip (ms)",-25} {acBinarySerMs + acBinaryDeserMs,12:F2} {msgPackSerMs + msgPackDeserMs,12:F2} {(acBinarySerMs + acBinaryDeserMs) / (msgPackSerMs + msgPackDeserMs),9:F2}x");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
var sizeDiff = msgPackData.Length - acBinaryData.Length;
|
var sizeDiff = msgPackData.Length - acBinaryData.Length;
|
||||||
if (sizeDiff > 0)
|
if (sizeDiff > 0)
|
||||||
Console.WriteLine($"[OK] AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)");
|
Console.WriteLine($"[OK] AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)");
|
||||||
|
|
@ -376,6 +377,114 @@ public class QuickBenchmark
|
||||||
|
|
||||||
#region Full Comparison (WithRef, NoRef, Populate, Merge)
|
#region Full Comparison (WithRef, NoRef, Populate, Merge)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[TestMethod]
|
||||||
|
public void GetAnalyzeStringInternCandidatesLog()
|
||||||
|
{
|
||||||
|
TestDataFactory.ResetIdCounter();
|
||||||
|
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||||
|
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||||
|
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||||
|
|
||||||
|
var testOrder = TestDataFactory.CreateOrder(
|
||||||
|
itemCount: 3,
|
||||||
|
palletsPerItem: 3,
|
||||||
|
measurementsPerPallet: 3,
|
||||||
|
pointsPerMeasurement: 4,
|
||||||
|
sharedTag: sharedTag,
|
||||||
|
sharedUser: sharedUser,
|
||||||
|
sharedMetadata: sharedMeta);
|
||||||
|
|
||||||
|
var options = AcBinarySerializerOptions.Default;
|
||||||
|
options.ReferenceHandling = ReferenceHandlingMode.OnlyId;
|
||||||
|
var analysisLog = AcBinarySerializer.GetAnalyzeStringInternCandidatesLog(testOrder,options);
|
||||||
|
|
||||||
|
Assert.IsNotNull(analysisLog);
|
||||||
|
Assert.IsGreaterThan(0, analysisLog.Length);
|
||||||
|
|
||||||
|
// Print results sorted by occurrence count
|
||||||
|
Console.WriteLine(analysisLog.ToString());
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void RunFullBenchmarkComparison2()
|
||||||
|
{
|
||||||
|
// Create test data with shared references
|
||||||
|
TestDataFactory.ResetIdCounter();
|
||||||
|
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||||
|
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||||
|
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||||
|
|
||||||
|
var testOrder = TestDataFactory.CreateOrder(
|
||||||
|
itemCount: 3,
|
||||||
|
palletsPerItem: 3,
|
||||||
|
measurementsPerPallet: 3,
|
||||||
|
pointsPerMeasurement: 4,
|
||||||
|
sharedTag: sharedTag,
|
||||||
|
sharedUser: sharedUser,
|
||||||
|
sharedMetadata: sharedMeta);
|
||||||
|
|
||||||
|
var singleOptions = AcBinarySerializerOptions.FastMode;
|
||||||
|
singleOptions.UseStringInterning = false;
|
||||||
|
|
||||||
|
Console.WriteLine("=== MINIMAL WARMUP TEST ===");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Wait for tiered JIT
|
||||||
|
//Console.WriteLine("Waiting 3s for tiered JIT...");
|
||||||
|
|
||||||
|
// MINIMAL WARMUP: Just 1 call to populate caches
|
||||||
|
Console.WriteLine("Single warmup call (cache only)...");
|
||||||
|
for (int i = 0; i < DefaultIterations; i++)
|
||||||
|
{
|
||||||
|
_ = AcBinarySerializer.Serialize(testOrder, singleOptions);
|
||||||
|
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||||
|
}
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("--- MEASURED TESTS (5x) ---");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var results = new double[5];
|
||||||
|
for (int test = 0; test < 5; test++)
|
||||||
|
{
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < DefaultIterations; i++)
|
||||||
|
_ = AcBinarySerializer.Serialize(testOrder, singleOptions);
|
||||||
|
results[test] = sw.Elapsed.TotalMilliseconds;
|
||||||
|
Console.WriteLine($"AcBinary Test{test + 1}: {results[test]:F2}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
var msgResults = new double[5];
|
||||||
|
for (int test = 0; test < 5; test++)
|
||||||
|
{
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < DefaultIterations; i++)
|
||||||
|
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||||
|
msgResults[test] = sw.Elapsed.TotalMilliseconds;
|
||||||
|
Console.WriteLine($"MsgPack Test{test + 1}: {msgResults[test]:F2}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("--- ANALYSIS ---");
|
||||||
|
var acBinaryAvg = results.Average();
|
||||||
|
var acBinaryVariance = results.Max() - results.Min();
|
||||||
|
var msgPackAvg = msgResults.Average();
|
||||||
|
var msgPackVariance = msgResults.Max() - msgResults.Min();
|
||||||
|
|
||||||
|
Console.WriteLine($"AcBinary: avg={acBinaryAvg:F2}ms, variance={acBinaryVariance:F2}ms ({100 * acBinaryVariance / acBinaryAvg:F1}%)");
|
||||||
|
Console.WriteLine($"MsgPack: avg={msgPackAvg:F2}ms, variance={msgPackVariance:F2}ms ({100 * msgPackVariance / msgPackAvg:F1}%)");
|
||||||
|
|
||||||
|
var isVarianceOk = acBinaryVariance <= acBinaryAvg * 0.2;
|
||||||
|
Console.WriteLine(isVarianceOk
|
||||||
|
? "[OK] Minimal warmup (1 call + 3s delay) is SUFFICIENT!"
|
||||||
|
: "[PROBLEM] Minimal warmup is NOT enough - need more iterations");
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void RunFullBenchmarkComparison()
|
public void RunFullBenchmarkComparison()
|
||||||
{
|
{
|
||||||
|
|
@ -397,23 +506,32 @@ public class QuickBenchmark
|
||||||
sharedMetadata: sharedMeta);
|
sharedMetadata: sharedMeta);
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
var withRefOptions = new AcBinarySerializerOptions();
|
var withRefOptions = AcBinarySerializerOptions.Default;
|
||||||
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
//withRefOptions.UseStringInterning = false;
|
||||||
|
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
// Warmup
|
noRefOptions.UseStringInterning = false;
|
||||||
Console.WriteLine("\nWarming up...");
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
|
||||||
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
|
||||||
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-serialize
|
// Pre-serialize
|
||||||
var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||||
var acBinaryNoRef = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
var acBinaryNoRef = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||||
var msgPackData = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
var msgPackData = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||||
|
|
||||||
|
// Warmup - MUST be 1000+ iterations for tiered JIT to complete
|
||||||
|
Console.WriteLine($"\nSerialize warming up ({DefaultIterations} iterations each)...");
|
||||||
|
for (int i = 0; i < 500; i++)
|
||||||
|
{
|
||||||
|
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||||
|
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||||
|
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||||
|
|
||||||
|
_ = AcBinaryDeserializer.Deserialize<TestOrder>(acBinaryWithRef);
|
||||||
|
_ = AcBinaryDeserializer.Deserialize<TestOrder>(acBinaryNoRef);
|
||||||
|
_ = MessagePackSerializer.Deserialize<TestOrder>(msgPackData, MsgPackOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for tiered JIT background compilation to complete
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
|
||||||
Console.WriteLine($"Iterations: {DefaultIterations:N0}");
|
Console.WriteLine($"Iterations: {DefaultIterations:N0}");
|
||||||
|
|
||||||
// Size comparison
|
// Size comparison
|
||||||
|
|
@ -519,7 +637,7 @@ public class QuickBenchmark
|
||||||
sharedMetadata: sharedMeta);
|
sharedMetadata: sharedMeta);
|
||||||
|
|
||||||
var withRefOptions = new AcBinarySerializerOptions();
|
var withRefOptions = new AcBinarySerializerOptions();
|
||||||
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
|
|
||||||
// Warmup
|
// Warmup
|
||||||
Console.WriteLine("Warming up...");
|
Console.WriteLine("Warming up...");
|
||||||
|
|
@ -602,7 +720,7 @@ public class QuickBenchmark
|
||||||
measurementsPerPallet: 3,
|
measurementsPerPallet: 3,
|
||||||
pointsPerMeasurement: 4);
|
pointsPerMeasurement: 4);
|
||||||
|
|
||||||
var options = AcBinarySerializerOptions.WithoutReferenceHandling();
|
var options = AcBinarySerializerOptions.WithoutReferenceHandling;
|
||||||
var binaryData = AcBinarySerializer.Serialize(testOrder, options);
|
var binaryData = AcBinarySerializer.Serialize(testOrder, options);
|
||||||
|
|
||||||
Console.WriteLine("Warming up...");
|
Console.WriteLine("Warming up...");
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using AyCode.Core.Serializers.Expressions;
|
using AyCode.Core.Serializers.Expressions;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using LExpression = System.Linq.Expressions.Expression;
|
using LExpression = System.Linq.Expressions.Expression;
|
||||||
using LExpressionType = System.Linq.Expressions.ExpressionType;
|
using LExpressionType = System.Linq.Expressions.ExpressionType;
|
||||||
|
|
||||||
|
|
@ -538,8 +537,13 @@ public static class AcSerializerCommon
|
||||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||||
var propAccess = LExpression.Property(castExpr, prop);
|
var propAccess = LExpression.Property(castExpr, prop);
|
||||||
var convertExpr = LExpression.Convert(propAccess, typeof(TProperty));
|
|
||||||
return LExpression.Lambda<Func<object, TProperty>>(convertExpr, objParam).Compile();
|
// Only convert if property type differs from TProperty (avoids unnecessary boxing)
|
||||||
|
Expression resultExpr = prop.PropertyType == typeof(TProperty)
|
||||||
|
? propAccess
|
||||||
|
: LExpression.Convert(propAccess, typeof(TProperty));
|
||||||
|
|
||||||
|
return LExpression.Lambda<Func<object, TProperty>>(resultExpr, objParam).Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
|
public abstract class AcSerializerOptions
|
||||||
|
{
|
||||||
|
public abstract AcSerializerType SerializerType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference handling mode for circular/shared references.
|
||||||
|
/// Default: OnlyId (JSON serializer requires All mode, OnlyId not yet implemented)
|
||||||
|
/// Note: Binary serializer supports OnlyId mode for IId-only tracking.
|
||||||
|
/// </summary>
|
||||||
|
public ReferenceHandlingMode ReferenceHandling { get; set; } = ReferenceHandlingMode.OnlyId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum depth for serialization/deserialization.
|
||||||
|
/// 0 = root level only (primitives of root object)
|
||||||
|
/// 1 = root + first level of nested objects/collections
|
||||||
|
/// byte.MaxValue (255) = effectively unlimited
|
||||||
|
/// Default: byte.MaxValue
|
||||||
|
/// </summary>
|
||||||
|
public byte MaxDepth { get; init; } = byte.MaxValue;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw exception on circular reference detection for non-IId types.
|
||||||
|
/// When true: Tracks all objects and throws InvalidOperationException on circular references.
|
||||||
|
/// When false: No tracking for non-IId types (faster, but circular refs may cause MaxDepth truncation).
|
||||||
|
/// Default: true (production safety)
|
||||||
|
/// Note: IId types are always tracked when ReferenceHandling != None.
|
||||||
|
/// </summary>
|
||||||
|
public bool ThrowOnCircularReference { get; init; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional callback for custom property mapping during cross-type operations.
|
||||||
|
/// Used when deserializing/populating with Deserialize<TSource, TDest> or Populate<TSource, TDest>.
|
||||||
|
///
|
||||||
|
/// Use cases:
|
||||||
|
/// - Mapping between external DTOs and internal models (different class hierarchies)
|
||||||
|
/// - Handling property renames across versions
|
||||||
|
/// - Custom property pairing logic
|
||||||
|
///
|
||||||
|
/// If null (default), properties are matched by name.
|
||||||
|
/// Callback is invoked once during mapping build phase and result is cached.
|
||||||
|
///
|
||||||
|
/// Performance: ZERO overhead on same-type operations (Deserialize<T>).
|
||||||
|
/// </summary>
|
||||||
|
public PropertyMapperDelegate? PropertyMapper { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AcSerializerType : byte
|
||||||
|
{
|
||||||
|
Json = 0,
|
||||||
|
Binary = 1,
|
||||||
|
Toon = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference handling mode for serialization.
|
||||||
|
/// </summary>
|
||||||
|
public enum ReferenceHandlingMode : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No reference handling - all objects serialized inline.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference handling only for IId objects - uses semantic Id for deduplication.
|
||||||
|
/// NOTE: Not fully implemented for JSON serializer - use All instead.
|
||||||
|
/// Binary serializer supports this mode.
|
||||||
|
/// </summary>
|
||||||
|
OnlyId = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full reference handling for all objects.
|
||||||
|
/// </summary>
|
||||||
|
All = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate for custom property mapping during cross-type deserialization/population.
|
||||||
|
/// Enables mapping between different class hierarchies or renamed properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceProperty">Property from the source type being deserialized</param>
|
||||||
|
/// <param name="destinationType">Target type being populated</param>
|
||||||
|
/// <returns>Mapped destination property, or null to skip this property</returns>
|
||||||
|
public delegate PropertyInfo? PropertyMapperDelegate(PropertyInfo sourceProperty, Type destinationType);
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using static AyCode.Core.Serializers.AcSerializerCommon;
|
using static AyCode.Core.Serializers.AcSerializerCommon;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Binaries;
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
@ -77,6 +76,21 @@ public static partial class AcBinarySerializer
|
||||||
private int[]? _propertyIndexBuffer;
|
private int[]? _propertyIndexBuffer;
|
||||||
private byte[]? _propertyStateBuffer;
|
private byte[]? _propertyStateBuffer;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
/// <summary>
|
||||||
|
/// DEBUG ONLY: Current property path being serialized (e.g., "Order.Status").
|
||||||
|
/// Used for string interning analysis.
|
||||||
|
/// </summary>
|
||||||
|
internal string? CurrentPropertyPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DEBUG ONLY: Callback invoked when a string is registered for interning.
|
||||||
|
/// Parameters: (propertyPath, stringValue)
|
||||||
|
/// Use this to analyze which properties have repeated string values.
|
||||||
|
/// </summary>
|
||||||
|
internal Action<string?, string>? OnStringInterned;
|
||||||
|
#endif
|
||||||
|
|
||||||
// These properties delegate to Options for convenience
|
// These properties delegate to Options for convenience
|
||||||
public bool UseStringInterning => Options.UseStringInterning;
|
public bool UseStringInterning => Options.UseStringInterning;
|
||||||
public bool UseMetadata => Options.UseMetadata;
|
public bool UseMetadata => Options.UseMetadata;
|
||||||
|
|
@ -1161,6 +1175,48 @@ public static partial class AcBinarySerializer
|
||||||
_position += length;
|
_position += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optimized FixStr write: tries SIMD ASCII conversion, falls back to UTF8.
|
||||||
|
/// Single-pass: uses Ascii.FromUtf16 which does validation + copy.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void WriteFixStrDirect(string value)
|
||||||
|
{
|
||||||
|
var length = value.Length;
|
||||||
|
EnsureCapacity(1 + length);
|
||||||
|
|
||||||
|
// Ascii.FromUtf16: SIMD-optimized ASCII conversion
|
||||||
|
// Returns actual bytes written - if less than input length, there was a non-ASCII char
|
||||||
|
var destSpan = _buffer.AsSpan(_position + 1, length);
|
||||||
|
var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten);
|
||||||
|
|
||||||
|
if (status == System.Buffers.OperationStatus.Done && bytesWritten == length)
|
||||||
|
{
|
||||||
|
// Success - write FixStr header
|
||||||
|
_buffer[_position] = BinaryTypeCode.EncodeFixStr(length);
|
||||||
|
_position += 1 + length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Non-ASCII or partial - use standard string encoding
|
||||||
|
_buffer[_position++] = BinaryTypeCode.String;
|
||||||
|
WriteStringUtf8Internal(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal string write (after String type code already written).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void WriteStringUtf8Internal(string value)
|
||||||
|
{
|
||||||
|
var byteCount = Utf8NoBom.GetByteCount(value);
|
||||||
|
WriteVarUInt((uint)byteCount);
|
||||||
|
EnsureCapacity(byteCount);
|
||||||
|
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
|
||||||
|
_position += byteCount;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write short UTF8 bytes using FixStr encoding.
|
/// Write short UTF8 bytes using FixStr encoding.
|
||||||
/// Only call when byteLength <= 31.
|
/// Only call when byteLength <= 31.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Serializers.Expressions;
|
using AyCode.Core.Serializers.Expressions;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
@ -44,6 +44,177 @@ public static partial class AcBinarySerializer
|
||||||
|
|
||||||
#region Public API
|
#region Public API
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
/// <summary>
|
||||||
|
/// DEBUG ONLY: Analyzes which string properties have repeated values that would benefit from interning.
|
||||||
|
/// Returns a dictionary where key is "TypeName.PropertyName" and value is the occurrence count.
|
||||||
|
/// Only properties with count > 1 are good candidates for [StringIntern] attribute.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The object graph to analyze.</param>
|
||||||
|
/// <param name="options">Serializer options (UseStringInterning should be enabled).</param>
|
||||||
|
/// <returns>Dictionary of property paths to their string occurrence counts.</returns>
|
||||||
|
public static Dictionary<string, Dictionary<string, int>> AnalyzeStringInternCandidates<T>(T value, AcBinarySerializerOptions? options = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
|
|
||||||
|
options ??= AcBinarySerializerOptions.Default;
|
||||||
|
|
||||||
|
// For analysis, use the provided reference handling mode
|
||||||
|
var analysisOptions = new AcBinarySerializerOptions
|
||||||
|
{
|
||||||
|
UseStringInterning = true,
|
||||||
|
MinStringInternLength = options.MinStringInternLength,
|
||||||
|
MaxStringInternLength = options.MaxStringInternLength,
|
||||||
|
ReferenceHandling = options.ReferenceHandling
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new Dictionary<string, Dictionary<string, int>>();
|
||||||
|
var runtimeType = value.GetType();
|
||||||
|
|
||||||
|
// Create context without pooling (we need to set up callback)
|
||||||
|
using var context = new BinarySerializationContext(analysisOptions);
|
||||||
|
|
||||||
|
// Set up tracking callbacks
|
||||||
|
context.OnStringInterned = (propertyPath, stringValue) =>
|
||||||
|
{
|
||||||
|
propertyPath ??= "(unknown)";
|
||||||
|
|
||||||
|
if (!result.TryGetValue(stringValue, out var properties))
|
||||||
|
{
|
||||||
|
properties = new Dictionary<string, int>();
|
||||||
|
result[stringValue] = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
properties[propertyPath] = properties.GetValueOrDefault(propertyPath) + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run serialization to trigger callbacks
|
||||||
|
context.WriteHeaderPlaceholder();
|
||||||
|
var estimatedHeaderSize = context.EstimateHeaderPayloadSize();
|
||||||
|
context.ReserveHeaderSpace(estimatedHeaderSize);
|
||||||
|
WriteValue(value, runtimeType, context, 0);
|
||||||
|
context.FinalizeHeaderSections();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringBuilder GetAnalyzeStringInternCandidatesLog<T>(T value, AcBinarySerializerOptions? options = null)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
options ??= AcBinarySerializerOptions.Default;
|
||||||
|
var analysis = AnalyzeStringInternCandidates(value, options);
|
||||||
|
|
||||||
|
// Transform: stringValue → properties TO propertyPath → (stringValue, count)
|
||||||
|
var propertyStats = new Dictionary<string, List<(string StringValue, int Count, int ByteLength)>>();
|
||||||
|
|
||||||
|
foreach (var (stringValue, properties) in analysis)
|
||||||
|
{
|
||||||
|
var byteLength = Encoding.UTF8.GetByteCount(stringValue);
|
||||||
|
foreach (var (propPath, count) in properties)
|
||||||
|
{
|
||||||
|
if (!propertyStats.TryGetValue(propPath, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
propertyStats[propPath] = list;
|
||||||
|
}
|
||||||
|
list.Add((stringValue, count, byteLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var refMode = options.ReferenceHandling;
|
||||||
|
|
||||||
|
// Header
|
||||||
|
sb.AppendLine("+==============================================================================+");
|
||||||
|
sb.AppendLine($"| STRING INTERN ANALYSIS REPORT (Mode: {refMode,-12}) |");
|
||||||
|
sb.AppendLine("+==============================================================================+");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Global summary
|
||||||
|
var totalStrings = analysis.Values.Sum(p => p.Values.Sum());
|
||||||
|
var uniqueStrings = analysis.Count;
|
||||||
|
var repeatedStrings = analysis.Count(kv => kv.Value.Values.Sum() > 1);
|
||||||
|
|
||||||
|
sb.AppendLine("+-----------------------------------------------------------------------------+");
|
||||||
|
sb.AppendLine("| STRING SUMMARY |");
|
||||||
|
sb.AppendLine("+-----------------------------------------------------------------------------+");
|
||||||
|
sb.AppendLine($"| Total string occurrences: {totalStrings,-10} Unique strings: {uniqueStrings,-10} Repeated: {repeatedStrings,-8} |");
|
||||||
|
sb.AppendLine("+-----------------------------------------------------------------------------+");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Property-focused table
|
||||||
|
// Calculate stats for each property first (for sorting by RepeatSum%)
|
||||||
|
var propertyStatsCalculated = propertyStats.Select(kv =>
|
||||||
|
{
|
||||||
|
var propPath = kv.Key;
|
||||||
|
var strings = kv.Value;
|
||||||
|
var total = strings.Sum(s => s.Count);
|
||||||
|
var unique = strings.Count;
|
||||||
|
var repeated = strings.Count(s => s.Count > 1);
|
||||||
|
var repeatSum = strings.Where(s => s.Count > 1).Sum(s => s.Count); // Sum of occurrences of repeated strings
|
||||||
|
var repeatSumPct = total > 0 ? repeatSum * 100.0 / total : 0;
|
||||||
|
|
||||||
|
// Calculate savings
|
||||||
|
var totalBytes = strings.Sum(s => s.Count * s.ByteLength);
|
||||||
|
var uniqueBytes = strings.Sum(s => s.ByteLength);
|
||||||
|
var indexBytes = total * 2;
|
||||||
|
var savings = totalBytes - (uniqueBytes + indexBytes);
|
||||||
|
|
||||||
|
return (propPath, strings, total, unique, repeated, repeatSum, repeatSumPct, savings);
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
sb.AppendLine("+--------------------------------+-------+---------+--------+---------+-----------+----------+-------------+");
|
||||||
|
sb.AppendLine("| Property | Total | RepSum | Unique | Repeated| RepSum % | Savings | Recommend |");
|
||||||
|
sb.AppendLine("+--------------------------------+-------+---------+--------+---------+-----------+----------+-------------+");
|
||||||
|
|
||||||
|
foreach (var stat in propertyStatsCalculated.OrderByDescending(x => x.repeatSumPct))
|
||||||
|
{
|
||||||
|
var savingsStr = stat.savings > 0 ? $"+{stat.savings:N0}B" : $"{stat.savings:N0}B";
|
||||||
|
var recommend = stat.savings > 100 ? "[INTERN]" : stat.savings > 0 ? " maybe " : " skip ";
|
||||||
|
|
||||||
|
var propDisplay = stat.propPath.Length > 30 ? stat.propPath[..27] + "..." : stat.propPath;
|
||||||
|
sb.AppendLine($"| {propDisplay,-30} | {stat.total,5} | {stat.repeatSum,7} | {stat.unique,6} | {stat.repeated,7} | {stat.repeatSumPct,8:F1}% | {savingsStr,8} | {recommend,-11} |");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("+--------------------------------+-------+---------+--------+---------+-----------+----------+-------------+");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Detailed property breakdown (only for properties with significant savings)
|
||||||
|
sb.AppendLine("+-----------------------------------------------------------------------------+");
|
||||||
|
sb.AppendLine("| DETAILED BREAKDOWN (properties with savings > 100 bytes) |");
|
||||||
|
sb.AppendLine("+-----------------------------------------------------------------------------+");
|
||||||
|
|
||||||
|
foreach (var stat in propertyStatsCalculated
|
||||||
|
.Where(x => x.savings > 100)
|
||||||
|
.OrderByDescending(x => x.repeatSumPct))
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($" {stat.propPath} (RepSum: {stat.repeatSum}/{stat.total} = {stat.repeatSumPct:F1}%):");
|
||||||
|
|
||||||
|
foreach (var (strVal, count, _) in stat.strings.OrderByDescending(s => s.Count).Take(10))
|
||||||
|
{
|
||||||
|
var preview = strVal.Length > 40 ? strVal[..37] + "..." : strVal;
|
||||||
|
var marker = count > 1 ? ">" : " ";
|
||||||
|
sb.AppendLine($" {marker} [{count,4}x] \"{preview}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat.strings.Count > 10)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" ... and {stat.strings.Count - 10} more unique values");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("===============================================================================");
|
||||||
|
sb.AppendLine("Legend: Total=all occurrences, RepSum=sum of repeated string occurrences");
|
||||||
|
sb.AppendLine(" Unique=distinct values, Repeated=count of values appearing 2+ times");
|
||||||
|
sb.AppendLine(" RepSum%=percentage of occurrences that are repeated (higher=better for intern)");
|
||||||
|
sb.AppendLine(" Savings=estimated bytes saved with interning (positive=good)");
|
||||||
|
sb.AppendLine(" > = repeated string (benefits from interning)");
|
||||||
|
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialize object to binary with default options.
|
/// Serialize object to binary with default options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -607,19 +778,25 @@ public static partial class AcBinarySerializer
|
||||||
&& (context.MaxStringInternLength == 0 || value.Length <= context.MaxStringInternLength))
|
&& (context.MaxStringInternLength == 0 || value.Length <= context.MaxStringInternLength))
|
||||||
{
|
{
|
||||||
var index = context.RegisterInternedString(value);
|
var index = context.RegisterInternedString(value);
|
||||||
|
#if DEBUG
|
||||||
|
context.OnStringInterned?.Invoke(context.CurrentPropertyPath, value);
|
||||||
|
#endif
|
||||||
context.WriteByte(BinaryTypeCode.StringInterned);
|
context.WriteByte(BinaryTypeCode.StringInterned);
|
||||||
context.WriteVarUInt((uint)index);
|
context.WriteVarUInt((uint)index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try FixStr for short ASCII strings (saves 1-2 bytes per string)
|
// Fast path for short strings: check length first (cheap), then ASCII
|
||||||
if (System.Text.Ascii.IsValid(value) && BinaryTypeCode.CanEncodeAsFixStr(value.Length))
|
// FixStr encodes type+length in single byte for strings <= 31 chars
|
||||||
|
var length = value.Length;
|
||||||
|
if (length <= BinaryTypeCode.FixStrMaxLength)
|
||||||
{
|
{
|
||||||
context.WriteFixStr(value);
|
// For short strings, use direct ASCII copy (avoids double validation)
|
||||||
|
context.WriteFixStrDirect(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard string encoding
|
// Long strings - standard encoding
|
||||||
context.WriteByte(BinaryTypeCode.String);
|
context.WriteByte(BinaryTypeCode.String);
|
||||||
context.WriteStringUtf8(value);
|
context.WriteStringUtf8(value);
|
||||||
}
|
}
|
||||||
|
|
@ -642,29 +819,29 @@ public static partial class AcBinarySerializer
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
|
|
||||||
// Wire format:
|
// Wire format:
|
||||||
// - IId types: [Object][props 0-tól...] - Id a props-ban, nincs extra
|
// - IId types: [Object][props 0-tól...] - Id a props-ban, nincs extra
|
||||||
// - Non-IId + All: [Object][hashcode][props 0-tól...] - hashcode elõre
|
// - Non-IId + All: [Object][hashcode][props 0-tól...] - hashcode előre
|
||||||
// - Ref=Off: [Object][props 0-tól...] - semmi extra
|
// - Ref=Off: [Object][props 0-tól...] - semmi extra
|
||||||
// ObjectRef format:
|
// ObjectRef format:
|
||||||
// - IId: [ObjectRef][Id érték]
|
// - IId: [ObjectRef][Id érték]
|
||||||
// - Non-IId: [ObjectRef][hashcode]
|
// - Non-IId: [ObjectRef][hashcode]
|
||||||
|
|
||||||
if (context.UseTypeReferenceHandling(metadata))
|
if (context.UseTypeReferenceHandling(metadata))
|
||||||
{
|
{
|
||||||
if (metadata.IsIId)
|
if (metadata.IsIId)
|
||||||
{
|
{
|
||||||
// IId típus: track by Id, ObjectRef writes Id
|
// IId típus: track by Id, ObjectRef writes Id
|
||||||
switch (metadata.IdAccessorType)
|
switch (metadata.IdAccessorType)
|
||||||
{
|
{
|
||||||
case AcSerializerCommon.IdAccessorType.Int32:
|
case AcSerializerCommon.IdAccessorType.Int32:
|
||||||
if (!context.TryTrack(wrapper, value, out int intId))
|
if (!context.TryTrack(wrapper, value, out int intId))
|
||||||
{
|
{
|
||||||
// Already seen ? ObjectRef + Id
|
// Already seen → ObjectRef + Id
|
||||||
context.WriteByte(BinaryTypeCode.ObjectRef);
|
context.WriteByte(BinaryTypeCode.ObjectRef);
|
||||||
context.WriteVarInt(intId);
|
context.WriteVarInt(intId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// First occurrence ? Object (no extra data, Id in props)
|
// First occurrence → Object (no extra data, Id in props)
|
||||||
context.WriteByte(BinaryTypeCode.Object);
|
context.WriteByte(BinaryTypeCode.Object);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -694,12 +871,12 @@ public static partial class AcBinarySerializer
|
||||||
// Non-IId + RefHandling=All: track by hashcode (IdAccessorType=Int32, RefIdGetter=GetHashCode)
|
// Non-IId + RefHandling=All: track by hashcode (IdAccessorType=Int32, RefIdGetter=GetHashCode)
|
||||||
if (!context.TryTrack(wrapper, value, out int hashcode))
|
if (!context.TryTrack(wrapper, value, out int hashcode))
|
||||||
{
|
{
|
||||||
// Already seen ? ObjectRef + hashcode
|
// Already seen → ObjectRef + hashcode
|
||||||
context.WriteByte(BinaryTypeCode.ObjectRef);
|
context.WriteByte(BinaryTypeCode.ObjectRef);
|
||||||
context.WriteVarInt(hashcode);
|
context.WriteVarInt(hashcode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// First occurrence ? Object + hashcode + props
|
// First occurrence → Object + hashcode + props
|
||||||
context.WriteByte(BinaryTypeCode.Object);
|
context.WriteByte(BinaryTypeCode.Object);
|
||||||
context.WriteVarInt(hashcode);
|
context.WriteVarInt(hashcode);
|
||||||
}
|
}
|
||||||
|
|
@ -846,6 +1023,16 @@ public static partial class AcBinarySerializer
|
||||||
context.WriteVarInt(enumValue);
|
context.WriteVarInt(enumValue);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case PropertyAccessorType.String:
|
||||||
|
{
|
||||||
|
// Fast path: typed getter, no boxing, no Type.GetTypeCode() call
|
||||||
|
var strValue = prop.GetString(obj);
|
||||||
|
if (strValue != null)
|
||||||
|
WriteString(strValue, context);
|
||||||
|
else
|
||||||
|
context.WriteByte(BinaryTypeCode.Null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Fallback to object getter for reference types
|
// Fallback to object getter for reference types
|
||||||
var value = prop.GetValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
|
|
@ -1002,6 +1189,23 @@ public static partial class AcBinarySerializer
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case PropertyAccessorType.String:
|
||||||
|
{
|
||||||
|
// Fast path: typed getter, no boxing, no Type.GetTypeCode() call
|
||||||
|
string? value = prop.GetString(obj);
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
context.WriteByte(value == null ? BinaryTypeCode.PropertySkip : BinaryTypeCode.StringEmpty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
context.CurrentPropertyPath = $"{prop.DeclaringType.Name}.{prop.Name}";
|
||||||
|
#endif
|
||||||
|
WriteString(value, context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// Object type - use regular getter
|
// Object type - use regular getter
|
||||||
|
|
@ -1015,6 +1219,9 @@ public static partial class AcBinarySerializer
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
context.CurrentPropertyPath = $"{prop.DeclaringType.Name}.{prop.Name}";
|
||||||
|
#endif
|
||||||
WriteValue(value, prop.PropertyType, context, depth);
|
WriteValue(value, prop.PropertyType, context, depth);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Binaries;
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
|
@ -23,14 +22,16 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default options instance with metadata and string interning enabled.
|
/// Default options instance with metadata and string interning enabled.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcBinarySerializerOptions Default = new();
|
public static AcBinarySerializerOptions Default => new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options optimized for maximum speed (no interning, no references).
|
/// Options optimized for maximum speed (no interning, no references).
|
||||||
/// Use when deserializer knows the exact type structure.
|
/// Use when deserializer knows the exact type structure.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcBinarySerializerOptions FastMode = new()
|
public static AcBinarySerializerOptions FastMode => new()
|
||||||
{
|
{
|
||||||
UseStringInterning = false,
|
UseStringInterning = false,
|
||||||
ReferenceHandling = ReferenceHandlingMode.None
|
ReferenceHandling = ReferenceHandlingMode.None
|
||||||
|
|
@ -38,8 +39,9 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options for shallow serialization (root level only).
|
/// Options for shallow serialization (root level only).
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcBinarySerializerOptions ShallowCopy = new()
|
public static AcBinarySerializerOptions ShallowCopy => new()
|
||||||
{
|
{
|
||||||
MaxDepth = 0,
|
MaxDepth = 0,
|
||||||
UseStringInterning = false,
|
UseStringInterning = false,
|
||||||
|
|
@ -48,8 +50,9 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options optimized for WASM environment with string caching enabled.
|
/// Options optimized for WASM environment with string caching enabled.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcBinarySerializerOptions WasmOptimized = new()
|
public static AcBinarySerializerOptions WasmOptimized => new()
|
||||||
{
|
{
|
||||||
IsWasm = true,
|
IsWasm = true,
|
||||||
UseStringCaching = true
|
UseStringCaching = true
|
||||||
|
|
@ -91,7 +94,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// Reduces size and memory for objects with many repeated string values.
|
/// Reduces size and memory for objects with many repeated string values.
|
||||||
/// Default: true
|
/// Default: true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseStringInterning { get; init; } = true;
|
public bool UseStringInterning { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimum string length to consider for interning.
|
/// Minimum string length to consider for interning.
|
||||||
|
|
@ -137,8 +140,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates options without reference handling (and string interning disabled for speed).
|
/// Creates options without reference handling (and string interning disabled for speed).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static AcBinarySerializerOptions WithoutReferenceHandling => new()
|
||||||
public static AcBinarySerializerOptions WithoutReferenceHandling() => new()
|
|
||||||
{
|
{
|
||||||
ReferenceHandling = ReferenceHandlingMode.None,
|
ReferenceHandling = ReferenceHandlingMode.None,
|
||||||
};
|
};
|
||||||
|
|
@ -146,213 +148,5 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates options without metadata (faster but less flexible).
|
/// Creates options without metadata (faster but less flexible).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static AcBinarySerializerOptions WithoutMetadata => new() { UseMetadata = false };
|
||||||
public static AcBinarySerializerOptions WithoutMetadata() => new() { UseMetadata = false };
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Binary type codes for serialization.
|
|
||||||
/// Designed for fast switch dispatch and compact storage.
|
|
||||||
/// Lower 5 bits = type code (0-31)
|
|
||||||
/// Upper 3 bits = flags (interned, reference, has-type-info)
|
|
||||||
/// </summary>
|
|
||||||
internal static class BinaryTypeCode
|
|
||||||
{
|
|
||||||
// Primitive types (0-15)
|
|
||||||
public const byte Null = 0;
|
|
||||||
public const byte True = 1;
|
|
||||||
public const byte False = 2;
|
|
||||||
public const byte Int8 = 3;
|
|
||||||
public const byte UInt8 = 4;
|
|
||||||
public const byte Int16 = 5;
|
|
||||||
public const byte UInt16 = 6;
|
|
||||||
public const byte Int32 = 7;
|
|
||||||
public const byte UInt32 = 8;
|
|
||||||
public const byte Int64 = 9;
|
|
||||||
public const byte UInt64 = 10;
|
|
||||||
public const byte Float32 = 11;
|
|
||||||
public const byte Float64 = 12;
|
|
||||||
public const byte Decimal = 13;
|
|
||||||
public const byte Char = 14;
|
|
||||||
|
|
||||||
// String types (16-19)
|
|
||||||
public const byte String = 16; // Inline UTF8 string
|
|
||||||
public const byte StringInterned = 17; // Reference to interned string by index
|
|
||||||
public const byte StringEmpty = 18; // Empty string marker
|
|
||||||
public const byte StringInternNew = 19; // New interned string - full content + register in table
|
|
||||||
|
|
||||||
// Date/Time types (20-23)
|
|
||||||
public const byte DateTime = 20;
|
|
||||||
public const byte DateTimeOffset = 21;
|
|
||||||
public const byte TimeSpan = 22;
|
|
||||||
public const byte Guid = 23;
|
|
||||||
|
|
||||||
// Enum (24)
|
|
||||||
public const byte Enum = 24;
|
|
||||||
|
|
||||||
// Complex types (25-31)
|
|
||||||
public const byte Object = 25; // Start of object
|
|
||||||
public const byte ObjectEnd = 26; // End of object marker
|
|
||||||
public const byte ObjectRef = 27; // Reference to previously serialized object
|
|
||||||
public const byte Array = 28; // Start of array/list
|
|
||||||
public const byte Dictionary = 29; // Start of dictionary
|
|
||||||
public const byte ByteArray = 30; // Optimized byte[] storage
|
|
||||||
|
|
||||||
// Special markers (32+, for header/meta)
|
|
||||||
// Header flags byte structure (for values >= 64):
|
|
||||||
// Bit 0 (0x01): HasMetadata
|
|
||||||
// Bit 1 (0x02): HasReferenceHandling
|
|
||||||
// Values 32, 33 are legacy for backward compatibility
|
|
||||||
public const byte MetadataHeader = 32; // Binary has metadata section (legacy, implies HasReferenceHandling=true)
|
|
||||||
public const byte NoMetadataHeader = 33; // Binary has no metadata (legacy, implies HasReferenceHandling=true)
|
|
||||||
|
|
||||||
// FixStr range: 34-65 (32 values for strings 0-31 bytes)
|
|
||||||
// FixStr encoding: FixStrBase + length (0-31)
|
|
||||||
// This saves 1 byte for short strings by combining type + length in single byte
|
|
||||||
public const byte FixStrBase = 34; // Base value for FixStr (0xA0 in MessagePack style, but we use 34)
|
|
||||||
public const byte FixStrMax = 65; // FixStrBase + 31 = maximum FixStr code
|
|
||||||
|
|
||||||
// New flag-based header markers (48+) - moved to after FixStr range
|
|
||||||
// Note: FixStr range 34-65 overlaps with old HeaderFlagsBase, so headers use version byte prefix
|
|
||||||
// Header byte structure: (marker & 0xF0) == HeaderFlagsBase, flags in (marker & 0x0F)
|
|
||||||
public const byte HeaderFlagsBase = 48; // Base value for flag-based headers (0x30)
|
|
||||||
public const byte HeaderFlag_Metadata = 0x01; // Bit 0: property metadata included
|
|
||||||
// Reference handling uses 2 separate bits:
|
|
||||||
// Bit 1 (0x02): OnlyId - reference handling for IId objects only
|
|
||||||
// Bit 2 (0x04): All - reference handling for all objects (includes OnlyId)
|
|
||||||
// None = both false, OnlyId = 0x02, All = 0x06 (both bits set)
|
|
||||||
public const byte HeaderFlag_RefHandling_OnlyId = 0x02;
|
|
||||||
public const byte HeaderFlag_RefHandling_All = 0x04;
|
|
||||||
public const byte HeaderFlag_HasFooterPosition = 0x08; // Bit 3: 4-byte footer position follows flags
|
|
||||||
|
|
||||||
// Compact integer variants (for VarInt optimization)
|
|
||||||
public const byte Int32Tiny = 192; // -16 to 63 stored in single byte (value = code - 192 - 16)
|
|
||||||
public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255)
|
|
||||||
|
|
||||||
// Property skip marker (for single-pass serialization optimization)
|
|
||||||
// CRITICAL: Must be in the "reserved" range 67-191 (after FixStr, before TinyInt)
|
|
||||||
// AND must not conflict with any other type codes.
|
|
||||||
// Using 191 (0xBF) - the highest value before TinyInt range starts at 192.
|
|
||||||
// This ensures it won't be confused with:
|
|
||||||
// - Primitive types (0-31)
|
|
||||||
// - FixStr (34-65)
|
|
||||||
// - TinyInt values (192-255)
|
|
||||||
public const byte PropertySkip = 191; // Marks a property with default/null value (skipped during serialization)
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if type code represents a reference (string or object).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool IsReference(byte code) => code is StringInterned or ObjectRef;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if type code is a FixStr (short string with length encoded in type code).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool IsFixStr(byte code) => code is >= FixStrBase and <= FixStrMax;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decode FixStr length from type code.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int DecodeFixStrLength(byte code) => code - FixStrBase;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encode FixStr type code for given byte length (0-31).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static byte EncodeFixStr(int byteLength) => (byte)(FixStrBase + byteLength);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if byte length can be encoded as FixStr.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool CanEncodeAsFixStr(int byteLength) => byteLength is >= 0 and <= 31;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check if type code is a tiny int (single byte int32 encoding).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool IsTinyInt(byte code) => code >= Int32Tiny;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decode tiny int value from type code.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int DecodeTinyInt(byte code) => code - Int32Tiny - 16;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encode small int value (-16 to 47) as type code.
|
|
||||||
/// Returns true if value fits in tiny encoding.
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool TryEncodeTinyInt(int value, out byte code)
|
|
||||||
{
|
|
||||||
// Range: -16 to 47 (64 values total, fitting in 192-255)
|
|
||||||
if (value is >= -16 and <= 47)
|
|
||||||
{
|
|
||||||
code = (byte)(value + 16 + Int32Tiny);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
code = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate used to decide whether a property should be serialized.
|
|
||||||
/// </summary>
|
|
||||||
public delegate bool BinaryPropertyFilter(in BinaryPropertyFilterContext context);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides property metadata and lazy value access for property filter evaluations.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct BinaryPropertyFilterContext
|
|
||||||
{
|
|
||||||
private readonly object? _instance;
|
|
||||||
private readonly Func<object, object?>? _valueGetter;
|
|
||||||
|
|
||||||
internal BinaryPropertyFilterContext(object? instance, Type declaringType, string propertyName, Type propertyType, Func<object, object?>? valueGetter)
|
|
||||||
{
|
|
||||||
_instance = instance;
|
|
||||||
DeclaringType = declaringType;
|
|
||||||
PropertyName = propertyName;
|
|
||||||
PropertyType = propertyType;
|
|
||||||
_valueGetter = valueGetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the declaring type of the property.
|
|
||||||
/// </summary>
|
|
||||||
public Type DeclaringType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the property name.
|
|
||||||
/// </summary>
|
|
||||||
public string PropertyName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the property type.
|
|
||||||
/// </summary>
|
|
||||||
public Type PropertyType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the instance being serialized when available. Null during metadata registration.
|
|
||||||
/// </summary>
|
|
||||||
public object? Instance => _instance;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the filter is invoked during metadata registration (when no instance is available).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsMetadataPhase => _instance is null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lazily obtains the current property value. Returns null when invoked during metadata registration.
|
|
||||||
/// </summary>
|
|
||||||
public object? GetValue()
|
|
||||||
{
|
|
||||||
if (_instance == null || _valueGetter == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _valueGetter(_instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate used to decide whether a property should be serialized.
|
||||||
|
/// </summary>
|
||||||
|
public delegate bool BinaryPropertyFilter(in BinaryPropertyFilterContext context);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides property metadata and lazy value access for property filter evaluations.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct BinaryPropertyFilterContext
|
||||||
|
{
|
||||||
|
private readonly object? _instance;
|
||||||
|
private readonly Func<object, object?>? _valueGetter;
|
||||||
|
|
||||||
|
internal BinaryPropertyFilterContext(object? instance, Type declaringType, string propertyName, Type propertyType, Func<object, object?>? valueGetter)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
DeclaringType = declaringType;
|
||||||
|
PropertyName = propertyName;
|
||||||
|
PropertyType = propertyType;
|
||||||
|
_valueGetter = valueGetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the declaring type of the property.
|
||||||
|
/// </summary>
|
||||||
|
public Type DeclaringType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property name.
|
||||||
|
/// </summary>
|
||||||
|
public string PropertyName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property type.
|
||||||
|
/// </summary>
|
||||||
|
public Type PropertyType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instance being serialized when available. Null during metadata registration.
|
||||||
|
/// </summary>
|
||||||
|
public object? Instance => _instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the filter is invoked during metadata registration (when no instance is available).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsMetadataPhase => _instance is null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lazily obtains the current property value. Returns null when invoked during metadata registration.
|
||||||
|
/// </summary>
|
||||||
|
public object? GetValue()
|
||||||
|
{
|
||||||
|
if (_instance == null || _valueGetter == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return _valueGetter(_instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary type codes for serialization.
|
||||||
|
/// Designed for fast switch dispatch and compact storage.
|
||||||
|
/// Lower 5 bits = type code (0-31)
|
||||||
|
/// Upper 3 bits = flags (interned, reference, has-type-info)
|
||||||
|
/// </summary>
|
||||||
|
internal static class BinaryTypeCode
|
||||||
|
{
|
||||||
|
// Primitive types (0-15)
|
||||||
|
public const byte Null = 0;
|
||||||
|
public const byte True = 1;
|
||||||
|
public const byte False = 2;
|
||||||
|
public const byte Int8 = 3;
|
||||||
|
public const byte UInt8 = 4;
|
||||||
|
public const byte Int16 = 5;
|
||||||
|
public const byte UInt16 = 6;
|
||||||
|
public const byte Int32 = 7;
|
||||||
|
public const byte UInt32 = 8;
|
||||||
|
public const byte Int64 = 9;
|
||||||
|
public const byte UInt64 = 10;
|
||||||
|
public const byte Float32 = 11;
|
||||||
|
public const byte Float64 = 12;
|
||||||
|
public const byte Decimal = 13;
|
||||||
|
public const byte Char = 14;
|
||||||
|
|
||||||
|
// String types (16-19)
|
||||||
|
public const byte String = 16; // Inline UTF8 string
|
||||||
|
public const byte StringInterned = 17; // Reference to interned string by index
|
||||||
|
public const byte StringEmpty = 18; // Empty string marker
|
||||||
|
public const byte StringInternNew = 19; // New interned string - full content + register in table
|
||||||
|
|
||||||
|
// Date/Time types (20-23)
|
||||||
|
public const byte DateTime = 20;
|
||||||
|
public const byte DateTimeOffset = 21;
|
||||||
|
public const byte TimeSpan = 22;
|
||||||
|
public const byte Guid = 23;
|
||||||
|
|
||||||
|
// Enum (24)
|
||||||
|
public const byte Enum = 24;
|
||||||
|
|
||||||
|
// Complex types (25-31)
|
||||||
|
public const byte Object = 25; // Start of object
|
||||||
|
public const byte ObjectEnd = 26; // End of object marker
|
||||||
|
public const byte ObjectRef = 27; // Reference to previously serialized object
|
||||||
|
public const byte Array = 28; // Start of array/list
|
||||||
|
public const byte Dictionary = 29; // Start of dictionary
|
||||||
|
public const byte ByteArray = 30; // Optimized byte[] storage
|
||||||
|
|
||||||
|
// Special markers (32+, for header/meta)
|
||||||
|
// Header flags byte structure (for values >= 64):
|
||||||
|
// Bit 0 (0x01): HasMetadata
|
||||||
|
// Bit 1 (0x02): HasReferenceHandling
|
||||||
|
// Values 32, 33 are legacy for backward compatibility
|
||||||
|
public const byte MetadataHeader = 32; // Binary has metadata section (legacy, implies HasReferenceHandling=true)
|
||||||
|
public const byte NoMetadataHeader = 33; // Binary has no metadata (legacy, implies HasReferenceHandling=true)
|
||||||
|
|
||||||
|
// FixStr range: 34-65 (32 values for strings 0-31 bytes)
|
||||||
|
// FixStr encoding: FixStrBase + length (0-31)
|
||||||
|
// This saves 1 byte for short strings by combining type + length in single byte
|
||||||
|
public const byte FixStrBase = 34; // Base value for FixStr (0xA0 in MessagePack style, but we use 34)
|
||||||
|
public const byte FixStrMax = 65; // FixStrBase + 31 = maximum FixStr code
|
||||||
|
public const int FixStrMaxLength = 31; // Maximum string length encodable as FixStr
|
||||||
|
|
||||||
|
// New flag-based header markers (48+) - moved to after FixStr range
|
||||||
|
// Note: FixStr range 34-65 overlaps with old HeaderFlagsBase, so headers use version byte prefix
|
||||||
|
// Header byte structure: (marker & 0xF0) == HeaderFlagsBase, flags in (marker & 0x0F)
|
||||||
|
public const byte HeaderFlagsBase = 48; // Base value for flag-based headers (0x30)
|
||||||
|
public const byte HeaderFlag_Metadata = 0x01; // Bit 0: property metadata included
|
||||||
|
// Reference handling uses 2 separate bits:
|
||||||
|
// Bit 1 (0x02): OnlyId - reference handling for IId objects only
|
||||||
|
// Bit 2 (0x04): All - reference handling for all objects (includes OnlyId)
|
||||||
|
// None = both false, OnlyId = 0x02, All = 0x06 (both bits set)
|
||||||
|
public const byte HeaderFlag_RefHandling_OnlyId = 0x02;
|
||||||
|
public const byte HeaderFlag_RefHandling_All = 0x04;
|
||||||
|
public const byte HeaderFlag_HasFooterPosition = 0x08; // Bit 3: 4-byte footer position follows flags
|
||||||
|
|
||||||
|
// Compact integer variants (for VarInt optimization)
|
||||||
|
public const byte Int32Tiny = 192; // -16 to 63 stored in single byte (value = code - 192 - 16)
|
||||||
|
public const byte Int32TinyMax = 255; // Upper bound for tiny int (192 + 64 - 1 = 255)
|
||||||
|
|
||||||
|
// Property skip marker (for single-pass serialization optimization)
|
||||||
|
// CRITICAL: Must be in the "reserved" range 67-191 (after FixStr, before TinyInt)
|
||||||
|
// AND must not conflict with any other type codes.
|
||||||
|
// Using 191 (0xBF) - the highest value before TinyInt range starts at 192.
|
||||||
|
// This ensures it won't be confused with:
|
||||||
|
// - Primitive types (0-31)
|
||||||
|
// - FixStr (34-65)
|
||||||
|
// - TinyInt values (192-255)
|
||||||
|
public const byte PropertySkip = 191; // Marks a property with default/null value (skipped during serialization)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if type code represents a reference (string or object).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsReference(byte code) => code is StringInterned or ObjectRef;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if type code is a FixStr (short string with length encoded in type code).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsFixStr(byte code) => code is >= FixStrBase and <= FixStrMax;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decode FixStr length from type code.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int DecodeFixStrLength(byte code) => code - FixStrBase;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode FixStr type code for given byte length (0-31).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static byte EncodeFixStr(int byteLength) => (byte)(FixStrBase + byteLength);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if byte length can be encoded as FixStr.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool CanEncodeAsFixStr(int byteLength) => byteLength is >= 0 and <= 31;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if type code is a tiny int (single byte int32 encoding).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsTinyInt(byte code) => code >= Int32Tiny;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decode tiny int value from type code.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int DecodeTinyInt(byte code) => code - Int32Tiny - 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode small int value (-16 to 47) as type code.
|
||||||
|
/// Returns true if value fits in tiny encoding.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool TryEncodeTinyInt(int value, out byte code)
|
||||||
|
{
|
||||||
|
// Range: -16 to 47 (64 values total, fitting in 192-255)
|
||||||
|
if (value is >= -16 and <= 47)
|
||||||
|
{
|
||||||
|
code = (byte)(value + 16 + Int32Tiny);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
code = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers;
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,7 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Jsons;
|
namespace AyCode.Core.Serializers.Jsons;
|
||||||
|
|
||||||
public enum AcSerializerType : byte
|
|
||||||
{
|
|
||||||
Json = 0,
|
|
||||||
Binary = 1,
|
|
||||||
Toon = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference handling mode for serialization.
|
|
||||||
/// </summary>
|
|
||||||
public enum ReferenceHandlingMode : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No reference handling - all objects serialized inline.
|
|
||||||
/// </summary>
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference handling only for IId objects - uses semantic Id for deduplication.
|
|
||||||
/// NOTE: Not fully implemented for JSON serializer - use All instead.
|
|
||||||
/// Binary serializer supports this mode.
|
|
||||||
/// </summary>
|
|
||||||
OnlyId = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Full reference handling for all objects.
|
|
||||||
/// </summary>
|
|
||||||
All = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate for custom property mapping during cross-type deserialization/population.
|
|
||||||
/// Enables mapping between different class hierarchies or renamed properties.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sourceProperty">Property from the source type being deserialized</param>
|
|
||||||
/// <param name="destinationType">Target type being populated</param>
|
|
||||||
/// <returns>Mapped destination property, or null to skip this property</returns>
|
|
||||||
public delegate PropertyInfo? PropertyMapperDelegate(PropertyInfo sourceProperty, Type destinationType);
|
|
||||||
|
|
||||||
public abstract class AcSerializerOptions
|
|
||||||
{
|
|
||||||
public abstract AcSerializerType SerializerType { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference handling mode for circular/shared references.
|
|
||||||
/// Default: OnlyId (JSON serializer requires All mode, OnlyId not yet implemented)
|
|
||||||
/// Note: Binary serializer supports OnlyId mode for IId-only tracking.
|
|
||||||
/// </summary>
|
|
||||||
public ReferenceHandlingMode ReferenceHandling { get; set; } = ReferenceHandlingMode.OnlyId;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum depth for serialization/deserialization.
|
|
||||||
/// 0 = root level only (primitives of root object)
|
|
||||||
/// 1 = root + first level of nested objects/collections
|
|
||||||
/// byte.MaxValue (255) = effectively unlimited
|
|
||||||
/// Default: byte.MaxValue
|
|
||||||
/// </summary>
|
|
||||||
public byte MaxDepth { get; init; } = byte.MaxValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Throw exception on circular reference detection for non-IId types.
|
|
||||||
/// When true: Tracks all objects and throws InvalidOperationException on circular references.
|
|
||||||
/// When false: No tracking for non-IId types (faster, but circular refs may cause MaxDepth truncation).
|
|
||||||
/// Default: true (production safety)
|
|
||||||
/// Note: IId types are always tracked when ReferenceHandling != None.
|
|
||||||
/// </summary>
|
|
||||||
public bool ThrowOnCircularReference { get; init; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional callback for custom property mapping during cross-type operations.
|
|
||||||
/// Used when deserializing/populating with Deserialize<TSource, TDest> or Populate<TSource, TDest>.
|
|
||||||
///
|
|
||||||
/// Use cases:
|
|
||||||
/// - Mapping between external DTOs and internal models (different class hierarchies)
|
|
||||||
/// - Handling property renames across versions
|
|
||||||
/// - Custom property pairing logic
|
|
||||||
///
|
|
||||||
/// If null (default), properties are matched by name.
|
|
||||||
/// Callback is invoked once during mapping build phase and result is cached.
|
|
||||||
///
|
|
||||||
/// Performance: ZERO overhead on same-type operations (Deserialize<T>).
|
|
||||||
/// </summary>
|
|
||||||
public PropertyMapperDelegate? PropertyMapper { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options for AcJsonSerializer and AcJsonDeserializer.
|
/// Options for AcJsonSerializer and AcJsonDeserializer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -96,13 +11,15 @@ public sealed class AcJsonSerializerOptions : AcSerializerOptions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default options instance with reference handling enabled and max depth.
|
/// Default options instance with reference handling enabled and max depth.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcJsonSerializerOptions Default = new() { ReferenceHandling = ReferenceHandlingMode.All };
|
public static AcJsonSerializerOptions Default => new() { ReferenceHandling = ReferenceHandlingMode.All };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options for shallow serialization (root level only, no references).
|
/// Options for shallow serialization (root level only, no references).
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcJsonSerializerOptions ShallowCopy = new() { MaxDepth = 0, ReferenceHandling = ReferenceHandlingMode.None };
|
public static AcJsonSerializerOptions ShallowCopy => new() { MaxDepth = 0, ReferenceHandling = ReferenceHandlingMode.None };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates options with specified max depth.
|
/// Creates options with specified max depth.
|
||||||
|
|
@ -112,5 +29,5 @@ public sealed class AcJsonSerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates options without reference handling.
|
/// Creates options without reference handling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AcJsonSerializerOptions WithoutReferenceHandling() => new() { ReferenceHandling = ReferenceHandlingMode.None };
|
public static AcJsonSerializerOptions WithoutReferenceHandling => new() { ReferenceHandling = ReferenceHandlingMode.None };
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ public abstract class PropertyAccessorBase : PropertyMetadataBase
|
||||||
private readonly Func<object, uint>? _uint32Getter;
|
private readonly Func<object, uint>? _uint32Getter;
|
||||||
private readonly Func<object, ulong>? _uint64Getter;
|
private readonly Func<object, ulong>? _uint64Getter;
|
||||||
private readonly Func<object, Guid>? _guidGetter;
|
private readonly Func<object, Guid>? _guidGetter;
|
||||||
|
private readonly Func<object, string?>? _stringGetter;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -82,6 +83,9 @@ public abstract class PropertyAccessorBase : PropertyMetadataBase
|
||||||
case PropertyAccessorType.Enum:
|
case PropertyAccessorType.Enum:
|
||||||
Unsafe.AsRef(in _int32Getter) = AcSerializerCommon.CreateEnumGetter(declaringType, prop);
|
Unsafe.AsRef(in _int32Getter) = AcSerializerCommon.CreateEnumGetter(declaringType, prop);
|
||||||
break;
|
break;
|
||||||
|
case PropertyAccessorType.String:
|
||||||
|
Unsafe.AsRef(in _stringGetter) = AcSerializerCommon.CreateTypedGetter<string?>(declaringType, prop);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,5 +133,8 @@ public abstract class PropertyAccessorBase : PropertyMetadataBase
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int GetEnumAsInt32(object obj) => _int32Getter!(obj);
|
public int GetEnumAsInt32(object obj) => _int32Getter!(obj);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public string? GetString(object obj) => _stringGetter!(obj);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ public enum PropertyAccessorType : byte
|
||||||
UInt32,
|
UInt32,
|
||||||
UInt64,
|
UInt64,
|
||||||
Guid,
|
Guid,
|
||||||
Enum
|
Enum,
|
||||||
|
String
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -137,6 +138,7 @@ public abstract class PropertyMetadataBase
|
||||||
TypeCode.UInt16 => PropertyAccessorType.UInt16,
|
TypeCode.UInt16 => PropertyAccessorType.UInt16,
|
||||||
TypeCode.UInt32 => PropertyAccessorType.UInt32,
|
TypeCode.UInt32 => PropertyAccessorType.UInt32,
|
||||||
TypeCode.UInt64 => PropertyAccessorType.UInt64,
|
TypeCode.UInt64 => PropertyAccessorType.UInt64,
|
||||||
|
TypeCode.String => PropertyAccessorType.String,
|
||||||
_ => PropertyAccessorType.Object
|
_ => PropertyAccessorType.Object
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Collections;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using static AyCode.Core.Helpers.JsonUtilities;
|
using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Toons;
|
namespace AyCode.Core.Serializers.Toons;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
|
|
||||||
//using ReferenceEqualityComparer = AyCode.Core.Serializers.ReferenceEqualityComparer;
|
//using ReferenceEqualityComparer = AyCode.Core.Serializers.ReferenceEqualityComparer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using static AyCode.Core.Helpers.JsonUtilities;
|
using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Toons;
|
namespace AyCode.Core.Serializers.Toons;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers.Toons;
|
namespace AyCode.Core.Serializers.Toons;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -136,8 +134,9 @@ public sealed class AcToonSerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Full mode: Meta + Data (first-time serialization).
|
/// Full mode: Meta + Data (first-time serialization).
|
||||||
/// Use when LLM needs complete context about data structure and values.
|
/// Use when LLM needs complete context about data structure and values.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcToonSerializerOptions Default = new()
|
public static AcToonSerializerOptions Default => new()
|
||||||
{
|
{
|
||||||
Mode = ToonSerializationMode.Full,
|
Mode = ToonSerializationMode.Full,
|
||||||
UseMeta = true,
|
UseMeta = true,
|
||||||
|
|
@ -150,8 +149,9 @@ public sealed class AcToonSerializerOptions : AcSerializerOptions
|
||||||
/// Meta-only mode: Only serialize type definitions and descriptions.
|
/// Meta-only mode: Only serialize type definitions and descriptions.
|
||||||
/// Use this to send schema information once at conversation start.
|
/// Use this to send schema information once at conversation start.
|
||||||
/// Subsequent serializations can use DataOnly mode to save tokens.
|
/// Subsequent serializations can use DataOnly mode to save tokens.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcToonSerializerOptions MetaOnly = new()
|
public static AcToonSerializerOptions MetaOnly => new()
|
||||||
{
|
{
|
||||||
Mode = ToonSerializationMode.MetaOnly,
|
Mode = ToonSerializationMode.MetaOnly,
|
||||||
UseMeta = true,
|
UseMeta = true,
|
||||||
|
|
@ -163,8 +163,9 @@ public sealed class AcToonSerializerOptions : AcSerializerOptions
|
||||||
/// Data-only mode: Only serialize actual data values.
|
/// Data-only mode: Only serialize actual data values.
|
||||||
/// Use this when schema was already sent via MetaOnly.
|
/// Use this when schema was already sent via MetaOnly.
|
||||||
/// Saves ~30-50% tokens in repeated serializations.
|
/// Saves ~30-50% tokens in repeated serializations.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcToonSerializerOptions DataOnly = new()
|
public static AcToonSerializerOptions DataOnly => new()
|
||||||
{
|
{
|
||||||
Mode = ToonSerializationMode.DataOnly,
|
Mode = ToonSerializationMode.DataOnly,
|
||||||
UseMeta = false,
|
UseMeta = false,
|
||||||
|
|
@ -176,8 +177,9 @@ public sealed class AcToonSerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compact mode: Minimal output, no meta, no indentation.
|
/// Compact mode: Minimal output, no meta, no indentation.
|
||||||
/// Maximum token efficiency but less readable.
|
/// Maximum token efficiency but less readable.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcToonSerializerOptions Compact = new()
|
public static AcToonSerializerOptions Compact => new()
|
||||||
{
|
{
|
||||||
Mode = ToonSerializationMode.DataOnly,
|
Mode = ToonSerializationMode.DataOnly,
|
||||||
UseMeta = false,
|
UseMeta = false,
|
||||||
|
|
@ -190,8 +192,9 @@ public sealed class AcToonSerializerOptions : AcSerializerOptions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verbose mode: Everything included (for debugging/documentation).
|
/// Verbose mode: Everything included (for debugging/documentation).
|
||||||
/// Use when you need maximum information and clarity.
|
/// Use when you need maximum information and clarity.
|
||||||
|
/// Returns a new instance each time to prevent shared state corruption.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly AcToonSerializerOptions Verbose = new()
|
public static AcToonSerializerOptions Verbose => new()
|
||||||
{
|
{
|
||||||
Mode = ToonSerializationMode.Full,
|
Mode = ToonSerializationMode.Full,
|
||||||
UseMeta = true,
|
UseMeta = true,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers;
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using AyCode.Services.Server.SignalRs;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
using MessagePack.Resolvers;
|
using MessagePack.Resolvers;
|
||||||
using AyCode.Core.Tests.Serialization;
|
using AyCode.Core.Tests.Serialization;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
|
|
||||||
namespace AyCode.Services.Server.Tests.SignalRs;
|
namespace AyCode.Services.Server.Tests.SignalRs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.Server.SignalRs;
|
using AyCode.Services.Server.SignalRs;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Services.SignalRs;
|
using AyCode.Services.SignalRs;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Tests.TestModels;
|
using AyCode.Core.Tests.TestModels;
|
||||||
using AyCode.Models.Server.DynamicMethods;
|
using AyCode.Models.Server.DynamicMethods;
|
||||||
using AyCode.Services.Server.SignalRs;
|
using AyCode.Services.Server.SignalRs;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using System.Collections;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
|
||||||
using AyCode.Core.Compression;
|
using AyCode.Core.Compression;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
|
|
||||||
namespace AyCode.Services.Server.SignalRs
|
namespace AyCode.Services.Server.SignalRs
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using AyCode.Core;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using AyCode.Models.Server.DynamicMethods;
|
using AyCode.Models.Server.DynamicMethods;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using AyCode.Core;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Loggers;
|
using AyCode.Core.Loggers;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Interfaces.Entities;
|
using AyCode.Interfaces.Entities;
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
|
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
|
||||||
using STJIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute;
|
using STJIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
|
|
||||||
namespace AyCode.Services.SignalRs;
|
namespace AyCode.Services.SignalRs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using AyCode.Core.Compression;
|
using AyCode.Core.Compression;
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
|
using AyCode.Core.Serializers;
|
||||||
using AyCode.Core.Serializers.Binaries;
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue