diff --git a/AyCode.Core/Serializers/AcSerializerCommon.cs b/AyCode.Core/Serializers/AcSerializerCommon.cs
index 1e50ad2..2351b6a 100644
--- a/AyCode.Core/Serializers/AcSerializerCommon.cs
+++ b/AyCode.Core/Serializers/AcSerializerCommon.cs
@@ -643,7 +643,11 @@ public static class AcSerializerCommon
{
return _tracked.TryAdd(key, null);
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool HasKey(TId key) => _tracked.ContainsKey(key);
+
///
/// Checks if key exists and returns existing value, or adds new key (deserialization).
/// Returns true if first occurrence (key was added, out = null).
@@ -669,7 +673,7 @@ public static class AcSerializerCommon
slot = newValue;
return newValue;
}
-
+
///
/// Tries to get the value for a key (ObjectRef lookup).
/// Returns true if found, false if not.
diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs
index f84ea3f..b464d9f 100644
--- a/AyCode.Core/Serializers/AcSerializerContextBase.cs
+++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs
@@ -53,7 +53,7 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM
{
// Get metadata from global cache (thread-safe)
var metadata = GlobalMetadataCache.GetOrAdd(type, MetadataFactory);
-
+
// Create wrapper with metadata + tracking state (per-context)
var wrapper = new TypeMetadataWrapper(metadata);
_wrappers[type] = wrapper;
@@ -64,6 +64,45 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM
#region Tracking API - int
+ [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);
+ }
+
+ [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);
+ }
+
+ [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);
+ }
+
///
/// Tries to track an object with int RefId.
/// Use when wrapper.Metadata.IdAccessorType == Int32.
@@ -78,10 +117,10 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM
refId = getter(obj);
// BitArray fast path for small positive IDs
- if (refId >= 0 && refId < MaxSmallId)
- {
- return TryTrackSmallId(wrapper, refId);
- }
+ //if (refId >= 0 && refId < MaxSmallId)
+ //{
+ // return TryTrackSmallId(wrapper, refId);
+ //}
// IdentityMap for large/negative IDs
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
index e47603c..3c09da7 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
@@ -19,7 +19,7 @@ public static partial class AcBinaryDeserializer
private int _position;
private List? _internedStrings;
private List? _propertyNames;
- private Dictionary? _objectReferences;
+ //private Dictionary? _objectReferences;
private Dictionary? _stringCache;
private readonly byte _minStringInternLength;
private readonly bool _useStringCaching;
@@ -66,7 +66,7 @@ public static partial class AcBinaryDeserializer
_position = 0;
_internedStrings = null;
_propertyNames = null;
- _objectReferences = null;
+ //_objectReferences = null;
_stringCache = null;
HasMetadata = false;
HasReferenceHandling = false;
@@ -522,33 +522,35 @@ public static partial class AcBinaryDeserializer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void RegisterObject(int refId, object instance)
+ public void RegisterObject(TypeMetadataWrapper wrapper, int refId, object instance)
{
- if (refId <= 0)
- {
- return;
- }
+ if (refId == 0) throw new Exception("refId == 0");
+ ContextClass.TryGetOrStoreInt32(wrapper, instance, refId);
+ //if (refId <= 0)
+ //{
+ // return;
+ //}
- _objectReferences ??= new Dictionary(16);
- _objectReferences[refId] = instance;
+ //_objectReferences ??= new Dictionary(16);
+ //_objectReferences[refId] = instance;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public object? GetReferencedObject(int refId)
- {
- if (refId <= 0)
- {
- return null;
- }
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //public object? GetReferencedObject(TypeMetadataWrapper wrapper, int refId)
+ //{
+ // //if (refId <= 0)
+ // //{
+ // // return null;
+ // //}
- if (_objectReferences == null || !_objectReferences.TryGetValue(refId, out var value))
- {
- throw new AcBinaryDeserializationException($"Unknown object reference id '{refId}'.", _position);
- }
+ // //if (_objectReferences == null || !_objectReferences.TryGetValue(refId, out var value))
+ // //{
+ // // throw new AcBinaryDeserializationException($"Unknown object reference id '{refId}'.", _position);
+ // //}
- return value;
- }
+ // //return value;
+ //}
private void EnsureAvailable(int length)
{
@@ -563,20 +565,20 @@ public static partial class AcBinaryDeserializer
var byteLength = (int)ReadVarUInt();
return ReadStringUtf8(byteLength);
}
-
+
#region IId Reference Cache - Delegates to ContextClass
-
+
///
/// After PopulateObject, checks if we should reuse an existing IId object.
/// Delegates to ContextClass which uses AcSerializerContextBase infrastructure.
/// Returns the object to use (either the new one or an existing cached one).
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public object GetOrRegisterIIdObject(object newObj, BinaryDeserializeTypeMetadata metadata)
- {
- return ContextClass.GetOrRegisterIIdObject(newObj, metadata);
- }
-
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //public object GetOrRegisterIIdObject(object newObj, TypeMetadataWrapper wrapper)
+ //{
+ // return ContextClass.GetOrRegisterIIdObject(newObj, wrapper);
+ //}
+
#endregion
}
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs
index 3069eec..4af38f9 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs
@@ -215,23 +215,20 @@ public static partial class AcBinaryDeserializer
///
private static object? ReadObjectWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth)
{
- var metadata = GetTypeMetadata(destType);
+ var wrapper = context.ContextClass.GetWrapper(destType);
+ var metadata = wrapper.Metadata;
// Handle reference ID if present
if (context.HasReferenceHandling)
{
var refId = context.ReadVarInt();
- if (refId < 0)
- {
- // Object reference - get existing object
- return context.GetReferencedObject(-refId);
- }
-
+ if (context.ContextClass.TryGetValue(wrapper, refId, out var instance)) return instance;
+
// New object with ID - will be registered below
- var instance = CreateInstance(destType, metadata);
+ instance = CreateInstance(destType, metadata);
if (instance != null && refId > 0)
{
- context.RegisterObject(refId, instance);
+ context.RegisterObject(wrapper, refId, instance);
}
PopulateObjectWithMapping(ref context, instance!, destType, indexMapping, depth);
@@ -258,7 +255,8 @@ public static partial class AcBinaryDeserializer
int[] indexMapping,
int depth)
{
- var metadata = GetTypeMetadata(destType);
+ var wrapper = context.ContextClass.GetWrapper(destType);
+ var metadata = wrapper.Metadata;
var propertyCount = (int)context.ReadVarUInt();
var nextDepth = depth + 1;
@@ -286,7 +284,7 @@ public static partial class AcBinaryDeserializer
}
// Reuse common populate logic
- PopulatePropertyValue(ref context, target, propInfo, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth);
+ PopulatePropertyValue(ref context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth);
}
}
@@ -311,6 +309,7 @@ public static partial class AcBinaryDeserializer
ref BinaryDeserializationContext context,
object target,
BinaryPropertySetterInfo propInfo,
+ TypeMetadataWrapper wrapper,
int nextDepth,
int sourcePropIndex,
int destPropIndex,
@@ -342,11 +341,11 @@ public static partial class AcBinaryDeserializer
var refId = context.ReadVarInt();
if (refId > 0)
{
- context.RegisterObject(refId, existingObj);
+ context.RegisterObject(wrapper, refId, existingObj);
}
}
- PopulateObjectCore(ref context, existingObj, GetTypeMetadata(propInfo.PropertyType), nextDepth, skipDefaultWrite: false);
+ PopulateObjectCore(ref context, existingObj, wrapper.Metadata, nextDepth, skipDefaultWrite: false);
return;
}
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
index 3846ef5..f76f7f3 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
@@ -12,13 +12,18 @@ public static partial class AcBinaryDeserializer
{
#region Populate Object Methods
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type)
+ => null;//MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t));
+
///
/// Populate object with automatic mode detection from context.
/// Uses IsMergeMode to determine merge behavior for IId collections.
///
private static void PopulateObject(ref BinaryDeserializationContext context, object target, Type targetType, int depth)
{
- var metadata = GetTypeMetadata(targetType);
+ var wrapper = context.ContextClass.GetWrapper(targetType);
+ var metadata = wrapper.Metadata;
// Handle ref ID if present
if (context.HasReferenceHandling)
@@ -26,7 +31,7 @@ public static partial class AcBinaryDeserializer
var refId = context.ReadVarInt();
if (refId > 0)
{
- context.RegisterObject(refId, target);
+ context.RegisterObject(wrapper, refId, target);
}
}
@@ -105,6 +110,8 @@ public static partial class AcBinaryDeserializer
var existingObj = propInfo.GetValue(target);
if (existingObj != null)
{
+ var wrapper = context.ContextClass.GetWrapper(propInfo.PropertyType);
+
context.ReadByte(); // consume Object marker
// Handle ref ID if present
@@ -113,12 +120,12 @@ public static partial class AcBinaryDeserializer
var refId = context.ReadVarInt();
if (refId > 0)
{
- context.RegisterObject(refId, existingObj);
+ context.RegisterObject(wrapper, refId, existingObj);
}
}
// Recursively populate - existing object, don't skip defaults
- PopulateObjectCore(ref context, existingObj, GetTypeMetadata(propInfo.PropertyType), nextDepth, skipDefaultWrite: false);
+ PopulateObjectCore(ref context, existingObj, wrapper.Metadata, nextDepth, skipDefaultWrite: false);
continue;
}
}
@@ -207,8 +214,10 @@ public static partial class AcBinaryDeserializer
try
{
+ var wrapper = context.ContextClass.GetWrapper(elementType);
var existingCount = existingList.Count;
- var elementMetadata = IsComplexType(elementType) ? GetTypeMetadata(elementType) : null;
+
+ var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
for (int i = 0; i < count; i++)
{
@@ -228,7 +237,7 @@ public static partial class AcBinaryDeserializer
var refId = context.ReadVarInt();
if (refId > 0)
{
- context.RegisterObject(refId, existingItem);
+ context.RegisterObject(wrapper, refId, existingItem);
}
}
@@ -302,7 +311,8 @@ public static partial class AcBinaryDeserializer
var arrayCount = (int)context.ReadVarUInt();
var nextDepth = depth + 1;
- var elementMetadata = GetTypeMetadata(elementType);
+ var wrapper = context.ContextClass.GetWrapper(elementType);
+ var elementMetadata = wrapper.Metadata;
// Track which IDs we see in source (for orphan removal)
HashSet
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsComplexType(Type type)
+ private static bool IsComplexType5(Type type)
{
if (type.IsPrimitive) return false;
if (ReferenceEquals(type, StringType)) return false;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
index 74149c6..95b56d5 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
@@ -325,15 +325,18 @@ public static partial class AcBinaryDeserializer
context.ReadByte();
// For top-level list merge, check if it's an IId collection
var elementType = GetCollectionElementType(targetType);
- if (elementType != null && IsComplexType(elementType))
+ if (elementType != null)
{
- var elementMetadata = GetTypeMetadata(elementType);
- if (elementMetadata.IsIId && elementMetadata.IdGetter != null)
+ var wrapper = context.ContextClass.GetWrapper(elementType);
+ var elementMetadata = wrapper.Metadata;
+
+ if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null)
{
- MergeIIdCollectionWithMetadata(ref context, targetList, elementType, elementMetadata, 0);
+ MergeIIdCollectionWithMetadata(ref context, targetList, elementType, wrapper, 0);
return;
}
}
+
// Non-IId collection, just populate
PopulateList(ref context, targetList, targetType, 0);
}
@@ -909,11 +912,11 @@ public static partial class AcBinaryDeserializer
///
private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth)
{
- var metadata = GetTypeMetadata(targetType);
+ var wrapper = context.ContextClass.GetWrapper(targetType);
+ var metadata = wrapper.Metadata;
- if (metadata.IsIId)
+ if (metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None)
{
- var wrapper = context.ContextClass.GetWrapper(targetType);
var map = wrapper.IdentityMap;
// Read ID based on type and lookup in IdentityMap
@@ -922,12 +925,11 @@ public static partial class AcBinaryDeserializer
AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, map),
AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, map),
AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, map),
- _ => context.GetReferencedObject(context.ReadVarInt())
+ _ => throw new Exception("metadata.IdAccessorType not valid")
};
}
- // Non-IId: sequential int refId
- return context.GetReferencedObject(context.ReadVarInt());
+ return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -936,7 +938,7 @@ public static partial class AcBinaryDeserializer
var id = context.ReadVarInt();
if (map is AcSerializerCommon.IdentityMap intMap && intMap.TryGetValue(id, out var obj))
return obj;
- return context.GetReferencedObject(id);
+ return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -959,36 +961,53 @@ public static partial class AcBinaryDeserializer
private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth)
{
- var metadata = GetTypeMetadata(targetType);
-
- // Read reference ID based on IdAccessorType (different wire format for each type)
- object? readId = null;
- if (context.HasReferenceHandling)
- {
- readId = metadata.IdAccessorType switch
- {
- AcSerializerCommon.IdAccessorType.Int32 => context.ReadVarInt(),
- AcSerializerCommon.IdAccessorType.Int64 => context.ReadVarLong(),
- AcSerializerCommon.IdAccessorType.Guid => context.ReadGuidUnsafe(),
- _ => throw new Exception($"metadata.IdAccessorType not valid: {metadata.IdAccessorType}")
- };
- }
-
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
{
return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth);
}
+
+ var wrapper = context.ContextClass.GetWrapper(targetType);
+ var metadata = wrapper.Metadata;
- // Create instance
- var instance = CreateInstance(targetType, metadata);
- if (instance == null) return null;
+ object? instance = null;
- // Register reference for flat lookup (int refId only)
- if (readId is int intRefId)
+ if (context.HasReferenceHandling && metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None)
{
- if (intRefId == 0) throw new Exception("intRefId == 0");
- context.RegisterObject(intRefId, instance);
+ switch (metadata.IdAccessorType)
+ {
+ case AcSerializerCommon.IdAccessorType.Int32:
+ var intId = context.ReadVarInt();
+ if (context.ContextClass.TryGetValue(wrapper, intId, out instance)) return instance;
+
+ instance = CreateInstance(targetType, metadata);
+ if (instance == null) return null;
+ context.ContextClass.TryGetOrStoreInt32(wrapper, instance, intId);
+ break;
+ case AcSerializerCommon.IdAccessorType.Int64:
+ var longId = context.ReadVarLong();
+ if (context.ContextClass.TryGetValue(wrapper, longId, out instance)) return instance;
+
+ instance = CreateInstance(targetType, metadata);
+ if (instance == null) return null;
+ context.ContextClass.TryGetOrStoreLong(wrapper, instance, longId);
+ break;
+ case AcSerializerCommon.IdAccessorType.Guid:
+ var guidId = context.ReadGuidUnsafe();
+ if (context.ContextClass.TryGetValue(wrapper, guidId, out instance)) return instance;
+
+ instance = CreateInstance(targetType, metadata);
+ if (instance == null) return null;
+ context.ContextClass.TryGetOrStoreGuid(wrapper, instance, guidId);
+ break;
+ default:
+ throw new Exception($"metadata.IdAccessorType not valid: {metadata.IdAccessorType}");
+ }
+ }
+ else
+ {
+ instance = CreateInstance(targetType, metadata);
+ if (instance == null) return null;
}
// Deserialize mode: object just created, skip writing defaults (already at default)
@@ -998,14 +1017,23 @@ public static partial class AcBinaryDeserializer
// Note: For ChainMode, we need IdGetter OR typed getters (IdAccessorType != None)
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
{
- object? id = metadata.IdAccessorType switch
+ object? id;
+ switch (metadata.IdAccessorType)
{
- AcSerializerCommon.IdAccessorType.Int32 => metadata.GetIdInt32(instance),
- AcSerializerCommon.IdAccessorType.Int64 => metadata.GetIdInt64(instance),
- AcSerializerCommon.IdAccessorType.Guid => metadata.GetIdGuid(instance),
- _ => null
- };
-
+ case AcSerializerCommon.IdAccessorType.Int32:
+ id = metadata.GetIdInt32(instance);
+ break;
+ case AcSerializerCommon.IdAccessorType.Int64:
+ id = metadata.GetIdInt64(instance);
+ break;
+ case AcSerializerCommon.IdAccessorType.Guid:
+ id = metadata.GetIdGuid(instance);
+ break;
+ default:
+ id = null;
+ break;
+ }
+
if (id != null && !IsDefaultValue(id, metadata.IdType))
{
// Check if we already have this object
@@ -1020,11 +1048,6 @@ public static partial class AcBinaryDeserializer
context.ChainTracker.TryRegisterIIdObject(instance);
}
}
- // Normal IId cache for non-chain deserialization
- else if (context.HasReferenceHandling && metadata.IsIId)
- {
- instance = context.GetOrRegisterIIdObject(instance, metadata);
- }
return instance;
}
@@ -1424,13 +1447,6 @@ public static partial class AcBinaryDeserializer
#region Type Metadata
- // Temporary: own cache until ref struct is removed
- private static readonly ConcurrentDictionary MetadataCache = new();
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type)
- => MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t));
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? CreateInstance(Type type, BinaryDeserializeTypeMetadata metadata)
{
diff --git a/AyCode.Core/Serializers/TypeMetadataBase.cs b/AyCode.Core/Serializers/TypeMetadataBase.cs
index 31b6ac1..a44d35b 100644
--- a/AyCode.Core/Serializers/TypeMetadataBase.cs
+++ b/AyCode.Core/Serializers/TypeMetadataBase.cs
@@ -102,8 +102,9 @@ public abstract class TypeMetadataBase
///
/// Typed getter delegate for IId.Id property.
/// Type depends on IdAccessorType (Func<object, int>, Func<object, long>, or Func<object, Guid>).
+ /// Cached here to avoid creating new delegates per wrapper.
///
- private readonly Delegate? _typedIdGetter;
+ public Delegate? TypedIdGetter { get; }
#region Scan Optimization Flags
@@ -112,7 +113,9 @@ public abstract class TypeMetadataBase
/// If false, ScanReferences can skip child property scanning entirely.
///
public bool HasComplexProperties { get; protected set; }
-
+
+ public bool IsComplexType { get; init; }
+
///
/// True if this type or any of its properties could potentially be shared (multi-referenced).
/// False for sealed value-like types with only primitives - these never need reference tracking.
@@ -125,19 +128,19 @@ public abstract class TypeMetadataBase
/// Gets the Id as int without boxing. Only valid when IdAccessorType == Int32.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int GetIdInt32(object obj) => ((Func