Refactor reference tracking to use per-type identity maps
Replaces flat object reference dictionary with per-type identity maps in deserializer, improving type safety and efficiency for IId types. TypeMetadataWrapper now uses cached typed delegates for reference ID access. Centralizes complex type detection and exposes IsComplexType and TypedIdGetter in metadata. Updates all registration and lookup logic to use wrappers, removes obsolete metadata cache, and ensures thread-safe, type-aware reference handling throughout. Comments out legacy code for easier review and rollback.
This commit is contained in:
parent
dc2526da7e
commit
6dbe4d76c1
|
|
@ -643,7 +643,11 @@ public static class AcSerializerCommon
|
|||
{
|
||||
return _tracked.TryAdd(key, null);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasKey(TId key) => _tracked.ContainsKey(key);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the value for a key (ObjectRef lookup).
|
||||
/// Returns true if found, false if not.
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public abstract class AcSerializerContextBase<TMetadata> 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<TMetadata>(metadata);
|
||||
_wrappers[type] = wrapper;
|
||||
|
|
@ -64,6 +64,45 @@ public abstract class AcSerializerContextBase<TMetadata> where TMetadata : TypeM
|
|||
|
||||
#region Tracking API - int
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TypeMetadataWrapper<TMetadata> wrapper, int refId, out object? instance)
|
||||
{
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<int>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<int>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
|
||||
return map.TryGetValue(refId, out instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TypeMetadataWrapper<TMetadata> wrapper, long refId, out object? instance)
|
||||
{
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<long>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<long>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
|
||||
return map.TryGetValue(refId, out instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TypeMetadataWrapper<TMetadata> wrapper, Guid refId, out object? instance)
|
||||
{
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<Guid>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<Guid>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
|
||||
return map.TryGetValue(refId, out instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with int RefId.
|
||||
/// Use when wrapper.Metadata.IdAccessorType == Int32.
|
||||
|
|
@ -78,10 +117,10 @@ public abstract class AcSerializerContextBase<TMetadata> 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<int>;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public static partial class AcBinaryDeserializer
|
|||
private int _position;
|
||||
private List<string>? _internedStrings;
|
||||
private List<string>? _propertyNames;
|
||||
private Dictionary<int, object>? _objectReferences;
|
||||
//private Dictionary<int, object>? _objectReferences;
|
||||
private Dictionary<int, string>? _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<BinaryDeserializeTypeMetadata> 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<int, object>(16);
|
||||
_objectReferences[refId] = instance;
|
||||
//_objectReferences ??= new Dictionary<int, object>(16);
|
||||
//_objectReferences[refId] = instance;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public object? GetReferencedObject(int refId)
|
||||
{
|
||||
if (refId <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public object? GetReferencedObject(TypeMetadataWrapper<BinaryDeserializeTypeMetadata> 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
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public object GetOrRegisterIIdObject(object newObj, BinaryDeserializeTypeMetadata metadata)
|
||||
{
|
||||
return ContextClass.GetOrRegisterIIdObject(newObj, metadata);
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//public object GetOrRegisterIIdObject(object newObj, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper)
|
||||
//{
|
||||
// return ContextClass.GetOrRegisterIIdObject(newObj, wrapper);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,23 +215,20 @@ public static partial class AcBinaryDeserializer
|
|||
/// </summary>
|
||||
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<BinaryDeserializeTypeMetadata> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
/// <summary>
|
||||
/// Populate object with automatic mode detection from context.
|
||||
/// Uses IsMergeMode to determine merge behavior for IId collections.
|
||||
/// </summary>
|
||||
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<object>? sourceIds = context.RemoveOrphanedItems && existingById != null
|
||||
|
|
@ -329,7 +339,7 @@ public static partial class AcBinaryDeserializer
|
|||
{
|
||||
var refId = context.ReadVarInt();
|
||||
if (refId > 0)
|
||||
context.RegisterObject(refId, newItem);
|
||||
context.RegisterObject(wrapper, refId, newItem);
|
||||
}
|
||||
|
||||
// Deserialize mode: new item just created, skip writing defaults
|
||||
|
|
@ -383,9 +393,10 @@ public static partial class AcBinaryDeserializer
|
|||
ref BinaryDeserializationContext context,
|
||||
IList existingList,
|
||||
Type elementType,
|
||||
BinaryDeserializeTypeMetadata elementMetadata,
|
||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||
int depth)
|
||||
{
|
||||
var elementMetadata = wrapper.Metadata;
|
||||
var idGetter = elementMetadata.IdGetter!;
|
||||
var idType = elementMetadata.IdType!;
|
||||
|
||||
|
|
@ -440,7 +451,7 @@ public static partial class AcBinaryDeserializer
|
|||
{
|
||||
var refId = context.ReadVarInt();
|
||||
if (refId > 0)
|
||||
context.RegisterObject(refId, newItem);
|
||||
context.RegisterObject(wrapper, refId, newItem);
|
||||
}
|
||||
|
||||
// Deserialize mode: new item just created, skip writing defaults
|
||||
|
|
@ -546,7 +557,7 @@ public static partial class AcBinaryDeserializer
|
|||
/// Determines if a type is a complex type (not primitive, string, or simple value type).
|
||||
/// </summary>
|
||||
[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;
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// </summary>
|
||||
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<int> 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<Type, BinaryDeserializeTypeMetadata> 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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -102,8 +102,9 @@ public abstract class TypeMetadataBase
|
|||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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.
|
||||
/// </summary>
|
||||
public bool HasComplexProperties { get; protected set; }
|
||||
|
||||
|
||||
public bool IsComplexType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int GetIdInt32(object obj) => ((Func<object, int>)_typedIdGetter!)(obj);
|
||||
public int GetIdInt32(object obj) => ((Func<object, int>)TypedIdGetter!)(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Id as long without boxing. Only valid when IdAccessorType == Int64.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long GetIdInt64(object obj) => ((Func<object, long>)_typedIdGetter!)(obj);
|
||||
public long GetIdInt64(object obj) => ((Func<object, long>)TypedIdGetter!)(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Id as Guid without boxing. Only valid when IdAccessorType == Guid.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Guid GetIdGuid(object obj) => ((Func<object, Guid>)_typedIdGetter!)(obj);
|
||||
public Guid GetIdGuid(object obj) => ((Func<object, Guid>)TypedIdGetter!)(obj);
|
||||
|
||||
protected TypeMetadataBase(Type type, Func<PropertyInfo, bool> ignorePropertyFilter)
|
||||
{
|
||||
|
|
@ -153,11 +156,14 @@ public abstract class TypeMetadataBase
|
|||
ReadableProperties = allReadable;
|
||||
WritableProperties = allReadable.Where(p => p.CanWrite).ToArray();
|
||||
|
||||
IsComplexType = IsComplexType2(type);
|
||||
|
||||
// Cache IId info at construction time - no runtime reflection needed later!
|
||||
var idInfo = GetIdInfo(type);
|
||||
IsIId = idInfo.IsId;
|
||||
IdType = idInfo.IdType;
|
||||
|
||||
|
||||
if (IsIId)
|
||||
{
|
||||
var idProp = type.GetProperty("Id");
|
||||
|
|
@ -167,17 +173,17 @@ public abstract class TypeMetadataBase
|
|||
if (ReferenceEquals(IdType, IntType))
|
||||
{
|
||||
IdAccessorType = AcSerializerCommon.IdAccessorType.Int32;
|
||||
_typedIdGetter = AcSerializerCommon.CreateTypedGetter<int>(type, idProp);
|
||||
TypedIdGetter = AcSerializerCommon.CreateTypedGetter<int>(type, idProp);
|
||||
}
|
||||
else if (ReferenceEquals(IdType, LongType))
|
||||
{
|
||||
IdAccessorType = AcSerializerCommon.IdAccessorType.Int64;
|
||||
_typedIdGetter = AcSerializerCommon.CreateTypedGetter<long>(type, idProp);
|
||||
TypedIdGetter = AcSerializerCommon.CreateTypedGetter<long>(type, idProp);
|
||||
}
|
||||
else if (ReferenceEquals(IdType, GuidType))
|
||||
{
|
||||
IdAccessorType = AcSerializerCommon.IdAccessorType.Guid;
|
||||
_typedIdGetter = AcSerializerCommon.CreateTypedGetter<Guid>(type, idProp);
|
||||
TypedIdGetter = AcSerializerCommon.CreateTypedGetter<Guid>(type, idProp);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -189,7 +195,7 @@ public abstract class TypeMetadataBase
|
|||
// Non-IId types: use RuntimeHelpers.GetHashCode (int)
|
||||
// RefIdGetter is created in TypeMetadataWrapper.CreateRefIdGetter()
|
||||
IdAccessorType = AcSerializerCommon.IdAccessorType.Int32;
|
||||
// _typedIdGetter remains null - wrapper uses GetHashCode directly
|
||||
// TypedIdGetter remains null - wrapper uses GetHashCode directly
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,4 +247,19 @@ public abstract class TypeMetadataBase
|
|||
return allProperties;
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsComplexType2(Type type)
|
||||
{
|
||||
if (type.IsPrimitive) return false;
|
||||
if (ReferenceEquals(type, StringType)) return false;
|
||||
if (type.IsEnum) return false;
|
||||
if (ReferenceEquals(type, GuidType)) return false;
|
||||
if (ReferenceEquals(type, DateTimeType)) return false;
|
||||
if (ReferenceEquals(type, DecimalType)) return false;
|
||||
if (ReferenceEquals(type, TimeSpanType)) return false;
|
||||
if (ReferenceEquals(type, DateTimeOffsetType)) return false;
|
||||
if (Nullable.GetUnderlyingType(type) != null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
/// <summary>
|
||||
/// Typed getter for reference ID. Runtime type is Func<object, int/long/Guid>.
|
||||
/// Use IdAccessorType to determine the actual type.
|
||||
/// For IId types: uses metadata.TypedIdGetter (cached in metadata).
|
||||
/// For non-IId types: uses RuntimeHelpers.GetHashCode.
|
||||
/// </summary>
|
||||
internal readonly Delegate RefIdGetter;
|
||||
|
||||
|
|
@ -36,39 +38,22 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
|
|||
|
||||
private const int BitArraySize = 1024; // 1024 * 64 = 65,536 IDs
|
||||
|
||||
/// <summary>
|
||||
/// Static fallback delegate for non-IId types.
|
||||
/// Shared across all wrappers to avoid allocation.
|
||||
/// </summary>
|
||||
private static readonly Func<object, int> HashCodeGetter = RuntimeHelpers.GetHashCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new wrapper for the given metadata.
|
||||
/// Initializes RefIdGetter based on IdAccessorType.
|
||||
/// Uses metadata.TypedIdGetter for IId types (cached, no allocation).
|
||||
/// </summary>
|
||||
public TypeMetadataWrapper(TMetadata metadata)
|
||||
{
|
||||
Metadata = metadata;
|
||||
|
||||
// Create typed RefIdGetter based on IdAccessorType
|
||||
RefIdGetter = CreateRefIdGetter(metadata);
|
||||
}
|
||||
|
||||
private static Delegate CreateRefIdGetter(TMetadata metadata)
|
||||
{
|
||||
if (metadata.IsIId && metadata.IdPropertyInfo != null)
|
||||
{
|
||||
// IId type - create typed getter from Id property
|
||||
return metadata.IdAccessorType switch
|
||||
{
|
||||
AcSerializerCommon.IdAccessorType.Int32 =>
|
||||
AcSerializerCommon.CreateTypedGetter<int>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
AcSerializerCommon.IdAccessorType.Int64 =>
|
||||
AcSerializerCommon.CreateTypedGetter<long>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
AcSerializerCommon.IdAccessorType.Guid =>
|
||||
AcSerializerCommon.CreateTypedGetter<Guid>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
_ => throw new NotSupportedException($"Unsupported IdAccessorType: {metadata.IdAccessorType}")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-IId type - use RuntimeHelpers.GetHashCode
|
||||
return new Func<object, int>(RuntimeHelpers.GetHashCode);
|
||||
}
|
||||
// Use cached delegate from metadata for IId types, static fallback for non-IId
|
||||
RefIdGetter = metadata.TypedIdGetter ?? HashCodeGetter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Reference in New Issue