11using System ;
22using System . Diagnostics . CodeAnalysis ;
33using System . Runtime . CompilerServices ;
4- using System . Threading ;
54
65namespace Nino . Core
76{
@@ -148,6 +147,9 @@ public static class CachedDeserializer<T>
148147 private static DeserializeDelegateRef < T > _deserializerRef ;
149148 private static readonly FastMap < int , DeserializeDelegate < T > > SubTypeDeserializers = new ( ) ;
150149 private static readonly FastMap < int , DeserializeDelegateRef < T > > SubTypeDeserializerRefs = new ( ) ;
150+ private static int _singleSubTypeId = int . MinValue ;
151+ private static DeserializeDelegate < T > _singleSubTypeDeserializer ;
152+ private static DeserializeDelegateRef < T > _singleSubTypeDeserializerRef ;
151153
152154 // Inline cache for polymorphic deserialization (separate caches for out/ref)
153155 // Cache expensive type checks
@@ -160,12 +162,6 @@ public static class CachedDeserializer<T>
160162 // ReSharper disable once StaticMemberInGenericType
161163 private static readonly bool IsSealed = typeof ( T ) . IsSealed || typeof ( T ) . IsValueType ;
162164
163- // Shared cache for polymorphic deserialization (Volatile for thread-safety)
164- private static int _cachedTypeId ;
165- private static int _cachedTypeIdRef ;
166- private static DeserializeDelegate < T > _cachedDeserializer ;
167- private static DeserializeDelegateRef < T > _cachedDeserializerRef ;
168-
169165 // ULTIMATE: JIT-eliminated constants for maximum performance
170166 // ReSharper disable once StaticMemberInGenericType
171167 internal static readonly bool IsSimpleType = ! IsReferenceOrContainsReferences && ! HasBaseType ;
@@ -178,6 +174,9 @@ public static void SetDeserializer(int typeId, DeserializeDelegate<T> deserializ
178174 _deserializerRef = deserializerRef ;
179175 SubTypeDeserializers . Add ( typeId , _deserializer ) ;
180176 SubTypeDeserializerRefs . Add ( typeId , _deserializerRef ) ;
177+ _singleSubTypeId = int . MinValue ;
178+ _singleSubTypeDeserializer = null ;
179+ _singleSubTypeDeserializerRef = null ;
181180 }
182181
183182 public static void AddSubTypeDeserializer < TSub > ( int subTypeId ,
@@ -189,13 +188,27 @@ public static void AddSubTypeDeserializer<TSub>(int subTypeId,
189188 SubTypeDeserializerWrapper < TSub > . RefDeserializer = deserializerRef ;
190189 SubTypeDeserializers . Add ( subTypeId , SubTypeDeserializerWrapper < TSub > . DeserializeOutWrapper ) ;
191190 SubTypeDeserializerRefs . Add ( subTypeId , SubTypeDeserializerWrapper < TSub > . DeserializeRefWrapper ) ;
191+
192+ if ( SubTypeDeserializers . Count == 2 )
193+ {
194+ _singleSubTypeId = subTypeId ;
195+ _singleSubTypeDeserializer = SubTypeDeserializerWrapper < TSub > . DeserializeOutWrapper ;
196+ _singleSubTypeDeserializerRef = SubTypeDeserializerWrapper < TSub > . DeserializeRefWrapper ;
197+ }
198+ else
199+ {
200+ _singleSubTypeId = int . MinValue ;
201+ _singleSubTypeDeserializer = null ;
202+ _singleSubTypeDeserializerRef = null ;
203+ }
192204 }
193205
194206 // Static wrapper class per TSub - allows better inlining than lambda
195207 private static class SubTypeDeserializerWrapper < TSub > where TSub : T
196208 {
197209 public static DeserializeDelegate < TSub > OutDeserializer ;
198210 public static DeserializeDelegateRef < TSub > RefDeserializer ;
211+ private static readonly bool IsValueType = typeof ( TSub ) . IsValueType ;
199212
200213 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
201214 public static void DeserializeOutWrapper ( out T value , ref Reader reader )
@@ -207,13 +220,15 @@ public static void DeserializeOutWrapper(out T value, ref Reader reader)
207220 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
208221 public static void DeserializeRefWrapper ( ref T value , ref Reader reader )
209222 {
210- if ( value is TSub val )
223+ if ( IsValueType )
211224 {
212- RefDeserializer ( ref val , ref reader ) ;
225+ TSub temp = default ;
226+ RefDeserializer ( ref temp , ref reader ) ;
227+ value = temp ;
213228 }
214229 else
215230 {
216- ThrowInvalidCast ( value ? . GetType ( ) ) ;
231+ RefDeserializer ( ref Unsafe . As < T , TSub > ( ref value ) , ref reader ) ;
217232 }
218233 }
219234 }
@@ -271,10 +286,6 @@ public static void Deserialize(out T value, ref Reader reader)
271286 // DIRECT DELEGATE: Generated code path - no polymorphism possible
272287 _deserializer ( out value , ref reader ) ;
273288 }
274- else if ( SubTypeDeserializers . Count == 1 )
275- {
276- SubTypeDeserializers . Values [ 0 ] ( out value , ref reader ) ;
277- }
278289 else
279290 {
280291 DeserializePolymorphic ( out value , ref reader ) ;
@@ -325,53 +336,26 @@ private static void DeserializePolymorphic(out T value, ref Reader reader)
325336 _deserializer ! ( out value , ref reader ) ;
326337 return ;
327338 }
328-
339+
329340 // FAST PATH: Exact type match for single subtype
330- if ( SubTypeDeserializers . Count == 2 )
341+ if ( typeId == _singleSubTypeId && _singleSubTypeDeserializer is not null )
331342 {
332- var keys = SubTypeDeserializers . Keys ;
333- if ( typeId == keys [ 0 ] )
334- {
335- SubTypeDeserializers . Values [ 0 ] ( out value , ref reader ) ;
336- return ;
337- }
338-
339- SubTypeDeserializers . Values [ 1 ] ( out value , ref reader ) ;
343+ _singleSubTypeDeserializer ( out value , ref reader ) ;
340344 return ;
341345 }
342-
346+
343347 // FAST PATH: Cache hit (optimized for monomorphic arrays)
344- #if NET5_0_OR_GREATER && ! UNITY_WEBGL
345- // On 64-bit platforms, Volatile is atomic and faster (~1-2 cycles)
346- var cachedId = Volatile . Read ( ref _cachedTypeId ) ;
347- if ( typeId == cachedId )
348- {
349- var cachedDeser = Volatile . Read ( ref _cachedDeserializer ) ;
350- cachedDeser ! ( out value , ref reader ) ;
351- return ;
352- }
353- #else
354- // On 32-bit platforms and WebGL, use Interlocked for atomicity (~10-20 cycles)
355- var cachedId = Interlocked . CompareExchange ( ref _cachedTypeId , 0 , 0 ) ;
356- if ( typeId == cachedId )
348+ if ( typeId == reader . CachedTypeId && reader . CachedDeserializer is DeserializeDelegate < T > cachedDeserializer )
357349 {
358- var cachedDeser = Interlocked . CompareExchange ( ref _cachedDeserializer , null , null ) ;
359- cachedDeser ! ( out value , ref reader ) ;
350+ cachedDeserializer ( out value , ref reader ) ;
360351 return ;
361352 }
362- #endif
363353
364354 // SLOW PATH: Full lookup in subtype map and update cache
365355 if ( SubTypeDeserializers . TryGetValue ( typeId , out var subTypeDeserializer ) )
366356 {
367- #if NET5_0_OR_GREATER && ! UNITY_WEBGL
368- // Update cache for subsequent elements
369- Volatile . Write ( ref _cachedTypeId , typeId ) ;
370- Volatile . Write ( ref _cachedDeserializer , subTypeDeserializer ) ;
371- #else
372- Interlocked . Exchange ( ref _cachedTypeId , typeId ) ;
373- Interlocked . Exchange ( ref _cachedDeserializer , subTypeDeserializer ) ;
374- #endif
357+ reader . CachedTypeId = typeId ;
358+ reader . CachedDeserializer = subTypeDeserializer ;
375359 subTypeDeserializer ( out value , ref reader ) ;
376360 return ;
377361 }
@@ -400,51 +384,25 @@ private static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
400384 }
401385
402386 // FAST PATH: Exact type match for single subtype
403- if ( SubTypeDeserializerRefs . Count == 2 )
387+ if ( typeId == _singleSubTypeId && _singleSubTypeDeserializerRef is not null )
404388 {
405- var keys = SubTypeDeserializerRefs . Keys ;
406- if ( typeId == keys [ 0 ] )
407- {
408- SubTypeDeserializerRefs . Values [ 0 ] ( ref value , ref reader ) ;
409- return ;
410- }
411-
412- SubTypeDeserializerRefs . Values [ 1 ] ( ref value , ref reader ) ;
389+ _singleSubTypeDeserializerRef ( ref value , ref reader ) ;
413390 return ;
414391 }
415392
416393 // FAST PATH: Cache hit (optimized for monomorphic arrays)
417- #if NET5_0_OR_GREATER && ! UNITY_WEBGL
418- // On 64-bit platforms, Volatile is atomic and faster (~1-2 cycles)
419- var cachedIdRef = Volatile . Read ( ref _cachedTypeIdRef ) ;
420- if ( typeId == cachedIdRef )
421- {
422- var cachedDeserRef = Volatile . Read ( ref _cachedDeserializerRef ) ;
423- cachedDeserRef ! ( ref value , ref reader ) ;
424- return ;
425- }
426- #else
427- // On 32-bit platforms and WebGL, use Interlocked for atomicity (~10-20 cycles)
428- var cachedIdRef = Interlocked . CompareExchange ( ref _cachedTypeIdRef , 0 , 0 ) ;
429- if ( typeId == cachedIdRef )
394+ if ( typeId == reader . CachedTypeIdRef &&
395+ reader . CachedDeserializerRef is DeserializeDelegateRef < T > cachedDeserializerRef )
430396 {
431- var cachedDeserRef = Interlocked . CompareExchange ( ref _cachedDeserializerRef , null , null ) ;
432- cachedDeserRef ! ( ref value , ref reader ) ;
397+ cachedDeserializerRef ( ref value , ref reader ) ;
433398 return ;
434399 }
435- #endif
436400
437401 // SLOW PATH: Full lookup in subtype map and update cache
438402 if ( SubTypeDeserializerRefs . TryGetValue ( typeId , out var subTypeDeserializer ) )
439403 {
440- #if NET5_0_OR_GREATER && ! UNITY_WEBGL
441- // Update cache for subsequent elements
442- Volatile . Write ( ref _cachedTypeIdRef , typeId ) ;
443- Volatile . Write ( ref _cachedDeserializerRef , subTypeDeserializer ) ;
444- #else
445- Interlocked . Exchange ( ref _cachedTypeIdRef , typeId ) ;
446- Interlocked . Exchange ( ref _cachedDeserializerRef , subTypeDeserializer ) ;
447- #endif
404+ reader . CachedTypeIdRef = typeId ;
405+ reader . CachedDeserializerRef = subTypeDeserializer ;
448406 subTypeDeserializer ( ref value , ref reader ) ;
449407 return ;
450408 }
0 commit comments