diff --git a/AyCode.Core/Serializers/AcSerializerCommon.cs b/AyCode.Core/Serializers/AcSerializerCommon.cs index 2351b6a..2f1dd75 100644 --- a/AyCode.Core/Serializers/AcSerializerCommon.cs +++ b/AyCode.Core/Serializers/AcSerializerCommon.cs @@ -1,10 +1,8 @@ -using System.Collections; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using AyCode.Core.Helpers; using AyCode.Core.Serializers.Expressions; using AyCode.Core.Serializers.Jsons; using LExpression = System.Linq.Expressions.Expression; @@ -60,19 +58,14 @@ public static class AcSerializerCommon /// /// Cache key type (usually Type) /// Cached value type - public sealed class ThreadLocalCache where TKey : notnull + public sealed class ThreadLocalCache(Func factory) + where TKey : notnull { private readonly ConcurrentDictionary _globalCache = new(); - private readonly Func _factory; - + [ThreadStatic] private static Dictionary? t_localCache; - public ThreadLocalCache(Func factory) - { - _factory = factory; - } - /// /// Gets a value from cache, creating it if necessary. /// Uses ThreadLocal cache for hot path, falls back to ConcurrentDictionary. @@ -94,7 +87,7 @@ public static class AcSerializerCommon [MethodImpl(MethodImplOptions.NoInlining)] private TValue GetSlow(TKey key) { - var value = _globalCache.GetOrAdd(key, _factory); + var value = _globalCache.GetOrAdd(key, factory); // Populate ThreadLocal cache var localCache = t_localCache ??= new Dictionary(); @@ -1033,109 +1026,104 @@ public static class AcSerializerCommon /// Used by cross-type deserialization (Deserialize<TSource, TDest>). /// Thread-safe and cached for performance. /// - public sealed class PropertyMappingCache - { - private readonly ConcurrentDictionary<(Type Source, Type Dest), PropertyMappingInfo> _cache = new(); + //public sealed class PropertyMappingCache + //{ + // private readonly ConcurrentDictionary<(Type Source, Type Dest), PropertyMappingInfo> _cache = new(); - /// - /// Gets or builds property mapping between two types. - /// Result is cached for subsequent calls. - /// - public PropertyMappingInfo GetOrBuild(Type sourceType, Type destType, PropertyMapperDelegate? customMapper, Func getMetadata) - { - var key = (sourceType, destType); - return _cache.GetOrAdd(key, _ => BuildMapping(sourceType, destType, customMapper, getMetadata)); - } + // /// + // /// Gets or builds property mapping between two types. + // /// Result is cached for subsequent calls. + // /// + // public PropertyMappingInfo GetOrBuild(Type sourceType, Type destType, PropertyMapperDelegate? customMapper, Func getMetadata) + // { + // var key = (sourceType, destType); + // return _cache.GetOrAdd(key, _ => BuildMapping(sourceType, destType, customMapper, getMetadata)); + // } - private static PropertyMappingInfo BuildMapping(Type sourceType, Type destType, PropertyMapperDelegate? customMapper, Func getMetadata) - { - var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.CanRead) - .ToArray(); + // private static PropertyMappingInfo BuildMapping(Type sourceType, Type destType, PropertyMapperDelegate? customMapper, Func getMetadata) + // { + // var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + // .Where(p => p.CanRead) + // .ToArray(); - var destProps = destType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.CanWrite) - .ToDictionary(p => p.Name, StringComparer.Ordinal); + // var destProps = destType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + // .Where(p => p.CanWrite) + // .ToDictionary(p => p.Name, StringComparer.Ordinal); - var mappings = new List<(PropertyInfo Source, PropertyInfo Dest)>(); + // var mappings = new List<(PropertyInfo Source, PropertyInfo Dest)>(); - foreach (var sourceProp in sourceProps) - { - PropertyInfo? destProp = null; + // foreach (var sourceProp in sourceProps) + // { + // PropertyInfo? destProp = null; - // Use custom mapper if provided - if (customMapper != null) - { - destProp = customMapper(sourceProp, destType); - } - else - { - // Default: match by name - destProps.TryGetValue(sourceProp.Name, out destProp); - } + // // Use custom mapper if provided + // if (customMapper != null) + // { + // destProp = customMapper(sourceProp, destType); + // } + // else + // { + // // Default: match by name + // destProps.TryGetValue(sourceProp.Name, out destProp); + // } - if (destProp != null && AreTypesCompatible(sourceProp.PropertyType, destProp.PropertyType)) - { - mappings.Add((sourceProp, destProp)); - } - } + // if (destProp != null && AreTypesCompatible(sourceProp.PropertyType, destProp.PropertyType)) + // { + // mappings.Add((sourceProp, destProp)); + // } + // } - return new PropertyMappingInfo(mappings.ToArray()); - } + // return new PropertyMappingInfo(mappings.ToArray()); + // } - /// - /// Checks if two property types are compatible for mapping. - /// Handles exact match, inheritance, nullable unwrapping, and numeric conversions. - /// - private static bool AreTypesCompatible(Type sourceType, Type destType) - { - // Exact match - if (sourceType == destType) return true; + // /// + // /// Checks if two property types are compatible for mapping. + // /// Handles exact match, inheritance, nullable unwrapping, and numeric conversions. + // /// + // private static bool AreTypesCompatible(Type sourceType, Type destType) + // { + // // Exact match + // if (sourceType == destType) return true; - // Assignable (inheritance, interfaces) - if (destType.IsAssignableFrom(sourceType)) return true; + // // Assignable (inheritance, interfaces) + // if (destType.IsAssignableFrom(sourceType)) return true; - // Unwrap nullable types - var sourceUnderlying = Nullable.GetUnderlyingType(sourceType) ?? sourceType; - var destUnderlying = Nullable.GetUnderlyingType(destType) ?? destType; + // // Unwrap nullable types + // var sourceUnderlying = Nullable.GetUnderlyingType(sourceType) ?? sourceType; + // var destUnderlying = Nullable.GetUnderlyingType(destType) ?? destType; - if (sourceUnderlying == destUnderlying) return true; + // if (sourceUnderlying == destUnderlying) return true; - // Numeric conversions (int -> long, float -> double, etc.) - if (IsNumericType(sourceUnderlying) && IsNumericType(destUnderlying)) - return true; + // // Numeric conversions (int -> long, float -> double, etc.) + // if (IsNumericType(sourceUnderlying) && IsNumericType(destUnderlying)) + // return true; - return false; - } + // return false; + // } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsNumericType(Type type) - { - return type == typeof(byte) || type == typeof(sbyte) || - type == typeof(short) || type == typeof(ushort) || - type == typeof(int) || type == typeof(uint) || - type == typeof(long) || type == typeof(ulong) || - type == typeof(float) || type == typeof(double) || - type == typeof(decimal); - } - } + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // private static bool IsNumericType(Type type) + // { + // return type == typeof(byte) || type == typeof(sbyte) || + // type == typeof(short) || type == typeof(ushort) || + // type == typeof(int) || type == typeof(uint) || + // type == typeof(long) || type == typeof(ulong) || + // type == typeof(float) || type == typeof(double) || + // type == typeof(decimal); + // } + //} /// /// Contains property mapping information for a source->destination type pair. /// Immutable and thread-safe. /// - public sealed class PropertyMappingInfo - { - public PropertyMappingInfo(IReadOnlyList<(PropertyInfo Source, PropertyInfo Dest)> mappings) - { - Mappings = mappings; - } - - /// - /// List of source->destination property pairs. - /// - public IReadOnlyList<(PropertyInfo Source, PropertyInfo Dest)> Mappings { get; } - } + //public sealed record PropertyMappingInfo(IReadOnlyList<(PropertyInfo Source, PropertyInfo Dest)> Mappings) + //{ + // /// + // /// List of source->destination property pairs. + // /// + // public IReadOnlyList<(PropertyInfo Source, PropertyInfo Dest)> Mappings { get; } = Mappings; + //} /// /// Binary-specific index-to-index mapping for cross-type deserialization. diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs index b464d9f..a3d4994 100644 --- a/AyCode.Core/Serializers/AcSerializerContextBase.cs +++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs @@ -30,9 +30,6 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM /// protected abstract Func MetadataFactory { get; } - private const int BitArraySize = 1024; - private const int MaxSmallId = BitArraySize * 64; - #region Wrapper Access /// @@ -67,149 +64,21 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(TypeMetadataWrapper wrapper, int refId, out object? instance) { - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - - return map.TryGetValue(refId, out instance); + return wrapper.TryGetValue(refId, out instance); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(TypeMetadataWrapper wrapper, long refId, out object? instance) { - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - - return map.TryGetValue(refId, out instance); + return wrapper.TryGetValue(refId, out instance); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(TypeMetadataWrapper wrapper, Guid refId, out object? instance) { - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - - return map.TryGetValue(refId, out instance); + return wrapper.TryGetValue(refId, out instance); } - /// - /// Tries to track an object with int RefId. - /// Use when wrapper.Metadata.IdAccessorType == Int32. - /// Returns true if first occurrence. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out int refId) - { - Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int32); - - var getter = (Func)wrapper.RefIdGetter; - refId = getter(obj); - - // BitArray fast path for small positive IDs - //if (refId >= 0 && refId < MaxSmallId) - //{ - // return TryTrackSmallId(wrapper, refId); - //} - - // IdentityMap for large/negative IDs - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryAddKey(refId); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryTrackSmallId(TypeMetadataWrapper wrapper, int id) - { - wrapper.EnsureSmallIdBitmap(); - var bitmap = wrapper.SmallIdBitmap!; - - uint idx = (uint)id; - uint wordIdx = idx >> 6; - int bitIdx = (int)(idx & 63); - - ulong mask = 1UL << bitIdx; - ref ulong word = ref bitmap[wordIdx]; - - if ((word & mask) == 0) - { - word |= mask; - return true; - } - - return false; - } - - #endregion - - #region Tracking API - long - - /// - /// Tries to track an object with long RefId. - /// Use when wrapper.Metadata.IdAccessorType == Int64. - /// Returns true if first occurrence. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out long refId) - { - Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int64); - - var getter = (Func)wrapper.RefIdGetter; - refId = getter(obj); - - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryAddKey(refId); - } - - #endregion - - #region Tracking API - Guid - - /// - /// Tries to track an object with Guid RefId. - /// Use when wrapper.Metadata.IdAccessorType == Guid. - /// Returns true if first occurrence. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out Guid refId) - { - Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Guid); - - var getter = (Func)wrapper.RefIdGetter; - refId = getter(obj); - - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryAddKey(refId); - } - - #endregion - - #region Deserialization API - TryGetOrStore - /// /// For deserialization: checks if an object with this Id was already seen. /// If yes, returns the existing object. If no, stores this object and returns it. @@ -218,15 +87,7 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM [MethodImpl(MethodImplOptions.AggressiveInlining)] public object TryGetOrStoreInt32(TypeMetadataWrapper wrapper, object newObj, int id) { - if (id == 0) return newObj; // Default Id - no tracking - - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryGetOrAddValue(id, newObj); + return id == 0 ? newObj : wrapper.TryGetOrStoreId(id, newObj); } /// @@ -235,15 +96,7 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM [MethodImpl(MethodImplOptions.AggressiveInlining)] public object TryGetOrStoreLong(TypeMetadataWrapper wrapper, object newObj, long id) { - if (id == 0) return newObj; - - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryGetOrAddValue(id, newObj); + return id == 0 ? newObj : wrapper.TryGetOrStoreId(id, newObj); } /// @@ -252,15 +105,7 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM [MethodImpl(MethodImplOptions.AggressiveInlining)] public object TryGetOrStoreGuid(TypeMetadataWrapper wrapper, object newObj, Guid id) { - if (id == Guid.Empty) return newObj; - - var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; - if (map == null) - { - map = new AcSerializerCommon.IdentityMap(); - wrapper.IdentityMap = map; - } - return map.TryGetOrAddValue(id, newObj); + return id == Guid.Empty ? newObj : wrapper.TryGetOrStoreId(id, newObj); } #endregion diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 2dfa6ca..92a9faf 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -48,7 +48,7 @@ public static partial class AcBinarySerializer /// /// Binary serialization context. Public for generated serializers. /// - internal sealed class BinarySerializationContext : AcSerializerContextBase, IDisposable + internal sealed class BinarySerializationContext : SerializationContextBase, IDisposable { private const int MinBufferSize = 256; private const int PropertyIndexBufferMaxCache = 512; @@ -65,7 +65,7 @@ public static partial class AcBinarySerializer private int _initialBufferSize; // Use shared reference tracker from AcSerializerCommon - private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new(); + //private readonly AcSerializerCommon.SerializationReferenceTracker _refTracker = new(); private Dictionary? _internedStrings; private List? _internedStringList; @@ -117,10 +117,10 @@ public static partial class AcBinarySerializer PropertyFilter = options.PropertyFilter; _initialBufferSize = Math.Max(options.InitialBufferCapacity, MinBufferSize); - _refTracker.Reset(); + //_refTracker.Reset(); if (UseReferenceHandling) { - _refTracker.EnsureInitialized(); + //_refTracker.EnsureInitialized(); } // Reset wrapper tracking state from base class (IId tracking) @@ -143,7 +143,7 @@ public static partial class AcBinarySerializer _bloomFilter2 = 0; _bloomFilter3 = 0; - _refTracker.Reset(); + //_refTracker.Reset(); ClearAndTrimIfNeeded(_internedStrings, InitialInternCapacity * 4); ClearAndTrimIfNeeded(_propertyNames, InitialPropertyNameCapacity * 4); @@ -1161,32 +1161,32 @@ public static partial class AcBinarySerializer #region Reference Handling - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public bool TrackForScanning(object obj) => _refTracker.TrackForScanning(obj); /// /// IId-aware tracking for the scan phase. /// First checks IId match (different instance, same Id), then falls back to ReferenceEquals. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TrackForScanningWithIId(object obj, BinarySerializeTypeMetadata metadata, out int existingRefId) - { - if (!UseReferenceHandling) - { - existingRefId = 0; - return true; // No tracking needed - } - return _refTracker.TrackForScanningWithIId(obj, metadata, out existingRefId); - } + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public bool TrackForScanningWithIId(object obj, BinarySerializeTypeMetadata metadata, out int existingRefId) + //{ + // if (!UseReferenceHandling) + // { + // existingRefId = 0; + // return true; // No tracking needed + // } + // return _refTracker.TrackForScanningWithIId(obj, metadata, out existingRefId); + //} - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ShouldWriteRef(object obj, out int refId) => _refTracker.ShouldWriteId(obj, out refId); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public bool ShouldWriteRef(object obj, out int refId) => _refTracker.ShouldWriteId(obj, out refId); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MarkAsWritten(object obj, int refId) => _refTracker.MarkAsWritten(obj, refId); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public void MarkAsWritten(object obj, int refId) => _refTracker.MarkAsWritten(obj, refId); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId); + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public bool TryGetExistingRef(object obj, out int refId) => _refTracker.TryGetExistingRef(obj, out refId); #endregion diff --git a/AyCode.Core/Serializers/DeserializationContextBase.cs b/AyCode.Core/Serializers/DeserializationContextBase.cs new file mode 100644 index 0000000..90037ef --- /dev/null +++ b/AyCode.Core/Serializers/DeserializationContextBase.cs @@ -0,0 +1,50 @@ +using System.Runtime.CompilerServices; + +namespace AyCode.Core.Serializers; + +/// +/// Abstract base class for all deserialization contexts (Binary, JSON, Toon). +/// Provides common deserialization operations: Id lookup, object storage by Id. +/// Derived classes are sealed for JIT devirtualization (direct call speed). +/// +/// The concrete metadata type for deserialization. +public abstract class DeserializationContextBase : AcSerializerContextBase + where TMetadata : TypeMetadataBase +{ + #region Object Lookup by Id (to be implemented by derived sealed classes) + + // Future: Abstract methods for object lookup/storage + // protected abstract object? GetByIdInt32Core(int id); + // protected abstract object? GetByIdInt64Core(long id); + // protected abstract object? GetByIdGuidCore(Guid id); + // protected abstract object GetOrStoreByIdInt32Core(int id, object newObj); + // protected abstract object GetOrStoreByIdInt64Core(long id, object newObj); + // protected abstract object GetOrStoreByIdGuidCore(Guid id, object newObj); + + #endregion + + #region Object Resolution (to be moved from AcSerializerContextBase) + + // Future: Common lookup/storage logic + // public object? GetById(int id) { ... } + // public object? GetById(long id) { ... } + // public object? GetById(Guid id) { ... } + // public object GetOrStoreById(int id, object newObj) { ... } + // public object GetOrStoreById(long id, object newObj) { ... } + // public object GetOrStoreById(Guid id, object newObj) { ... } + + #endregion + + #region Reset + + /// + /// Resets deserialization-specific state. Called by derived classes. + /// + public override void Reset() + { + base.Reset(); + // Future: Reset deserialization-specific state + } + + #endregion +} diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs index bac9048..b701c5f 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs @@ -35,7 +35,7 @@ public static partial class AcJsonSerializer } } - private sealed class JsonSerializationContext : AcSerializerContextBase, IDisposable + private sealed class JsonSerializationContext : SerializationContextBase, IDisposable { private readonly ArrayBufferWriter _buffer; public Utf8JsonWriter Writer { get; private set; } diff --git a/AyCode.Core/Serializers/ReferenceTracker.cs b/AyCode.Core/Serializers/ReferenceTracker.cs index 6ebb2fa..c7d27db 100644 --- a/AyCode.Core/Serializers/ReferenceTracker.cs +++ b/AyCode.Core/Serializers/ReferenceTracker.cs @@ -7,121 +7,121 @@ namespace AyCode.Core.Serializers; /// Shared reference tracking logic for serialization. /// Tracks object references to enable $id/$ref handling for circular references. /// -public sealed class SerializationReferenceTracker -{ - private readonly Dictionary _scanOccurrences; - private readonly Dictionary _writtenRefs; - private readonly HashSet _multiReferenced; - private int _nextId; +//public sealed class SerializationReferenceTracker +//{ +// private readonly Dictionary _scanOccurrences; +// private readonly Dictionary _writtenRefs; +// private readonly HashSet _multiReferenced; +// private int _nextId; - public SerializationReferenceTracker(int initialCapacity = 32) - { - _scanOccurrences = new(initialCapacity, ReferenceEqualityComparer.Instance); - _writtenRefs = new(initialCapacity, ReferenceEqualityComparer.Instance); - _multiReferenced = new(initialCapacity, ReferenceEqualityComparer.Instance); - _nextId = 1; - } +// public SerializationReferenceTracker(int initialCapacity = 32) +// { +// _scanOccurrences = new(initialCapacity, ReferenceEqualityComparer.Instance); +// _writtenRefs = new(initialCapacity, ReferenceEqualityComparer.Instance); +// _multiReferenced = new(initialCapacity, ReferenceEqualityComparer.Instance); +// _nextId = 1; +// } - /// - /// Tracks an object during the scanning phase. - /// Returns true if this is the first occurrence (should continue scanning children). - /// Returns false if object was seen before (multi-referenced). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TrackForScanning(object obj) - { - ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_scanOccurrences, obj, out var exists); - if (exists) - { - count++; - _multiReferenced.Add(obj); - return false; - } - count = 1; - return true; - } +// /// +// /// Tracks an object during the scanning phase. +// /// Returns true if this is the first occurrence (should continue scanning children). +// /// Returns false if object was seen before (multi-referenced). +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool TrackForScanning(object obj) +// { +// ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_scanOccurrences, obj, out var exists); +// if (exists) +// { +// count++; +// _multiReferenced.Add(obj); +// return false; +// } +// count = 1; +// return true; +// } - /// - /// Checks if an object should have an $id written and returns the id. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ShouldWriteId(object obj, out string id) - { - if (_multiReferenced.Contains(obj) && !_writtenRefs.ContainsKey(obj)) - { - id = _nextId++.ToString(); - return true; - } - id = ""; - return false; - } +// /// +// /// Checks if an object should have an $id written and returns the id. +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool ShouldWriteId(object obj, out string id) +// { +// if (_multiReferenced.Contains(obj) && !_writtenRefs.ContainsKey(obj)) +// { +// id = _nextId++.ToString(); +// return true; +// } +// id = ""; +// return false; +// } - /// - /// Marks an object as written with its assigned id. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MarkAsWritten(object obj, string id) => _writtenRefs[obj] = id; +// /// +// /// Marks an object as written with its assigned id. +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public void MarkAsWritten(object obj, string id) => _writtenRefs[obj] = id; - /// - /// Tries to get an existing reference id for an object. - /// If found, a $ref should be written instead of the full object. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetExistingRef(object obj, out string refId) - { - return _writtenRefs.TryGetValue(obj, out refId!); - } +// /// +// /// Tries to get an existing reference id for an object. +// /// If found, a $ref should be written instead of the full object. +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool TryGetExistingRef(object obj, out string refId) +// { +// return _writtenRefs.TryGetValue(obj, out refId!); +// } - /// - /// Clears all tracking data for reuse. - /// - public void Clear() - { - _scanOccurrences.Clear(); - _writtenRefs.Clear(); - _multiReferenced.Clear(); - _nextId = 1; - } -} +// /// +// /// Clears all tracking data for reuse. +// /// +// public void Clear() +// { +// _scanOccurrences.Clear(); +// _writtenRefs.Clear(); +// _multiReferenced.Clear(); +// _nextId = 1; +// } +//} -/// -/// Shared reference tracking logic for deserialization. -/// Resolves $id/$ref references during deserialization. -/// -public sealed class DeserializationReferenceTracker -{ - private Dictionary? _idToObject; +///// +///// Shared reference tracking logic for deserialization. +///// Resolves $id/$ref references during deserialization. +///// +//public sealed class DeserializationReferenceTracker +//{ +// private Dictionary? _idToObject; - /// - /// Registers an object with its $id. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RegisterObject(string id, object obj) - { - _idToObject ??= new Dictionary(64, StringComparer.Ordinal); - _idToObject[id] = obj; - } +// /// +// /// Registers an object with its $id. +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public void RegisterObject(string id, object obj) +// { +// _idToObject ??= new Dictionary(64, StringComparer.Ordinal); +// _idToObject[id] = obj; +// } - /// - /// Tries to get a referenced object by its $id. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetReferencedObject(string id, out object? obj) - { - if (_idToObject != null) - return _idToObject.TryGetValue(id, out obj); - obj = null; - return false; - } +// /// +// /// Tries to get a referenced object by its $id. +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public bool TryGetReferencedObject(string id, out object? obj) +// { +// if (_idToObject != null) +// return _idToObject.TryGetValue(id, out obj); +// obj = null; +// return false; +// } - /// - /// Clears all tracking data for reuse. - /// - public void Clear() - { - _idToObject?.Clear(); - } -} +// /// +// /// Clears all tracking data for reuse. +// /// +// public void Clear() +// { +// _idToObject?.Clear(); +// } +//} /// /// Reference equality comparer for object identity comparison. @@ -130,9 +130,9 @@ public sealed class DeserializationReferenceTracker public sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new(); - + private ReferenceEqualityComparer() { } - + public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); } diff --git a/AyCode.Core/Serializers/SerializationContextBase.cs b/AyCode.Core/Serializers/SerializationContextBase.cs new file mode 100644 index 0000000..7f037a6 --- /dev/null +++ b/AyCode.Core/Serializers/SerializationContextBase.cs @@ -0,0 +1,112 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace AyCode.Core.Serializers; + +/// +/// Abstract base class for all serialization contexts (Binary, JSON, Toon). +/// Provides common serialization operations: Id extraction, tracking first occurrence. +/// Derived classes are sealed for JIT devirtualization (direct call speed). +/// +/// The concrete metadata type for serialization. +public abstract class SerializationContextBase : AcSerializerContextBase + where TMetadata : TypeMetadataBase +{ + private const int BitArraySize = 1024; + private const int MaxSmallId = BitArraySize * 64; + + #region Id Extraction (to be implemented by derived sealed classes) + + // Future: Abstract methods for Id extraction + // protected abstract int GetIdInt32Core(object obj, TMetadata metadata); + // protected abstract long GetIdInt64Core(object obj, TMetadata metadata); + // protected abstract Guid GetIdGuidCore(object obj, TMetadata metadata); + + #endregion + + #region Tracking (to be moved from AcSerializerContextBase) + + /// + /// Tries to track an object with int RefId. + /// Use when wrapper.Metadata.IdAccessorType == Int32. + /// Returns true if first occurrence. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out int refId) + { + Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int32); + + var getter = (Func)wrapper.RefIdGetter; + refId = getter(obj); + + // BitArray fast path for small positive IDs + return refId is >= 0 and < MaxSmallId ? TryTrackSmallId(wrapper, refId) : wrapper.TryAddKey(refId); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryTrackSmallId(TypeMetadataWrapper wrapper, int id) + { + wrapper.EnsureSmallIdBitmap(); + var bitmap = wrapper.SmallIdBitmap!; + + var idx = (uint)id; + var wordIdx = idx >> 6; + var bitIdx = (int)(idx & 63); + + var mask = 1UL << bitIdx; + ref var word = ref bitmap[wordIdx]; + + if ((word & mask) != 0) return false; + + word |= mask; + return true; + } + + /// + /// Tries to track an object with long RefId. + /// Use when wrapper.Metadata.IdAccessorType == Int64. + /// Returns true if first occurrence. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out long refId) + { + Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int64); + + var getter = (Func)wrapper.RefIdGetter; + refId = getter(obj); + + return wrapper.TryAddKey(refId); + } + + /// + /// Tries to track an object with Guid RefId. + /// Use when wrapper.Metadata.IdAccessorType == Guid. + /// Returns true if first occurrence. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out Guid refId) + { + Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Guid); + + var getter = (Func)wrapper.RefIdGetter; + refId = getter(obj); + + return wrapper.TryAddKey(refId); + } + + #endregion + + #region Reset + + /// + /// Resets serialization-specific state. Called by derived classes. + /// + public override void Reset() + { + base.Reset(); + // Future: Reset serialization-specific state + } + + #endregion +} diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs index 90fc903..f088100 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using ReferenceEqualityComparer = AyCode.Core.Serializers.ReferenceEqualityComparer; +//using ReferenceEqualityComparer = AyCode.Core.Serializers.ReferenceEqualityComparer; namespace AyCode.Core.Serializers.Toons; @@ -47,7 +47,7 @@ public static partial class AcToonSerializer /// Pooled context for Toon serialization. /// Handles output building, indentation, and reference tracking. /// - private sealed class ToonSerializationContext : AcSerializerContextBase + private sealed class ToonSerializationContext : SerializationContextBase { private readonly StringBuilder _builder; private Dictionary? _scanOccurrences; diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs index 6e5b1ee..cb259de 100644 --- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs +++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs @@ -28,7 +28,7 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat /// /// Identity map for tracking. Runtime type is IdentityMap<int/long/Guid>. /// - internal AcSerializerCommon.IIdentityMap? IdentityMap; + protected AcSerializerCommon.IIdentityMap? IdentityMap; /// /// BitArray for tracking small int32 IDs (0-65535). @@ -77,4 +77,42 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat { SmallIdBitmap ??= new ulong[BitArraySize]; } + + /// + /// Gets or creates the typed IdentityMap for tracking. + /// Use: wrapper.GetOrCreateIdentityMap<int>().TryAddKey(id) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AcSerializerCommon.IdentityMap GetOrCreateIdentityMap() where TId : notnull + { + if (IdentityMap is AcSerializerCommon.IdentityMap typedMap) + return typedMap; + + var newMap = new AcSerializerCommon.IdentityMap(); + IdentityMap = newMap; + return newMap; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAddKey(TId key) where TId : struct + { + var map = GetOrCreateIdentityMap(); + return map.TryAddKey(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(TId refId, out object? instance) where TId : struct + { + var map = GetOrCreateIdentityMap(); + return map.TryGetValue(refId, out instance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object TryGetOrStoreId(TId id, object newObj) where TId : struct + { + //if (id == default(TId)) return newObj; // Default Id - no tracking + + var map = GetOrCreateIdentityMap(); + return map.TryGetOrAddValue(id, newObj); + } }