diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Copying.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Copying.cs index 8994e3f696..89e41d5925 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.Copying.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.Copying.cs @@ -372,7 +372,7 @@ public sealed partial class SerializationManager var generated = Unsafe.As>(source); var target = generated.Instantiate(); generated.Copy(ref target, this, hookCtx, context); - RunAfterHook(target, hookCtx); + TryRunAfterHook(target, hookCtx); return target; } else @@ -474,7 +474,7 @@ public sealed partial class SerializationManager var generated = Unsafe.As>(source); target ??= generated.Instantiate(); generated.Copy(ref target, this, hookCtx, context); - RunAfterHook(target, hookCtx); + TryRunAfterHook(target, hookCtx); return; } @@ -489,7 +489,7 @@ public sealed partial class SerializationManager target = CreateCopy(source, hookCtx, context); } - RunAfterHook(target, hookCtx); + TryRunAfterHook(target, hookCtx); } public void CopyTo(ITypeCopier copier, T source, ref T target, ISerializationContext? context = null, @@ -534,7 +534,7 @@ public sealed partial class SerializationManager } copier.CopyTo(this, source, ref target, DependencyCollection, hookCtx, context); - RunAfterHook(target, hookCtx); + TryRunAfterHook(target, hookCtx); } public void CopyTo(T source, ref T target, ISerializationContext? context = null, bool skipHook = false, bool notNullableOverride = false) @@ -607,13 +607,13 @@ public sealed partial class SerializationManager var generated = Unsafe.As>(source); var target = generated.Instantiate(); generated.Copy(ref target, this, hookCtx, context); - RunAfterHook(target, hookCtx); + TryRunAfterHook(target, hookCtx); return target; } var res = GetOrCreateCreateCopyGenericDelegate()(source, hookCtx, context); - RunAfterHook(res, hookCtx); + TryRunAfterHook(res, hookCtx); return res; } @@ -643,7 +643,7 @@ public sealed partial class SerializationManager } var res = copyCreator.CreateCopy(this, source, DependencyCollection, hookCtx, context); - RunAfterHook(res, hookCtx); + TryRunAfterHook(res, hookCtx); return res; } diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Reading.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Reading.cs index 7fc5e606fb..b2b5d27b9f 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.Reading.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.Reading.cs @@ -193,6 +193,7 @@ namespace Robust.Shared.Serialization.Manager { var baseType = typeof(T); var nullable = baseType.IsNullable(); + var isArray = ReadTypeMetadata.IsArray; T val = default!; @@ -321,7 +322,7 @@ namespace Robust.Shared.Serialization.Manager if (!hasSerializer) { - if (baseType.IsArray) + if (isArray) { val = node switch { @@ -381,7 +382,7 @@ namespace Robust.Shared.Serialization.Manager throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } - RunAfterHook(val, hookCtx); + TryRunAfterHook(val, hookCtx); } } } @@ -581,7 +582,8 @@ namespace Robust.Shared.Serialization.Manager } var baseType = typeof(T); - if (baseType.IsEnum || baseType.IsArray || + if (baseType.IsEnum || + ReadTypeMetadata.IsArray || (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(Nullable<>))) { return ((ReadGenericDelegate)_readGenericDelegates.GetOrAdd((typeof(T), node.GetType()!, notNullableOverride), @@ -771,7 +773,7 @@ namespace Robust.Shared.Serialization.Manager throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } - RunAfterHook(val, hookCtx); + TryRunAfterHook(val, hookCtx); } } } @@ -992,7 +994,7 @@ namespace Robust.Shared.Serialization.Manager throw new ArgumentException($"No mapping or value node provided for type {baseType}."); } - RunAfterHook(val, hookCtx); + TryRunAfterHook(val, hookCtx); } } } @@ -1427,7 +1429,7 @@ namespace Robust.Shared.Serialization.Manager throw new ArgumentException($"No mapping node provided for type {type} at line: {node.Start.Line}"); } - RunAfterHook(instance, hookCtx); + TryRunAfterHook(instance, hookCtx); return instance; } @@ -1447,7 +1449,7 @@ namespace Robust.Shared.Serialization.Manager definition.Populate(ref instance, node, this, hookCtx, context); - RunAfterHook(instance, hookCtx); + TryRunAfterHook(instance, hookCtx); return instance; } @@ -1456,5 +1458,16 @@ namespace Robust.Shared.Serialization.Manager { throw new ArgumentException($"No type serializer or data definition found for type {typeof(TValue)} with node type {node.GetType()} when reading"); } + + private static class ReadTypeMetadata + { + // ReSharper disable once StaticMemberInGenericType + public static bool IsArray; + + static ReadTypeMetadata() + { + IsArray = typeof(T).IsArray; + } + } } } diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.SerializerProvider.cs b/Robust.Shared/Serialization/Manager/SerializationManager.SerializerProvider.cs index e5a0d8cbf0..f297ac2553 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.SerializerProvider.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.SerializerProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; @@ -10,6 +11,9 @@ using Robust.Shared.Log; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Exceptions; using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Serialization.TypeSerializers.Interfaces; using Robust.Shared.Utility; @@ -31,6 +35,28 @@ public sealed partial class SerializationManager typeof(ITypeWriter<>) }.ToImmutableArray(); + private static readonly ImmutableArray Nodes = new[] + { + typeof(MappingDataNode), + typeof(SequenceDataNode), + typeof(ValueDataNode), + }.ToImmutableArray(); + + /// + /// + /// + private const int ReaderIndex = 0; + + /// + /// + /// + private const int InheritanceHandlerIndex = 1; + + /// + /// + /// + private const int ValidatorIndex = 2; + /// /// /// @@ -41,12 +67,42 @@ public sealed partial class SerializationManager /// private const int CopierIndex = 4; + /// + /// + /// + private const int WriterIndex = 5; + + /// + /// + /// + private const int MappingIndex = 0; + + /// + /// + /// + private const int SequenceIndex = 1; + + /// + /// + /// + private const int ValueIndex = 2; + private SerializerProvider _regularSerializerProvider = default!; private ISawmill _serializerSawmill = default!; private void InitializeTypeSerializers(IEnumerable typeSerializers) { + DebugTools.AssertEqual(ReaderIndex, SerializerInterfaces.IndexOf(typeof(ITypeReader<,>))); + DebugTools.AssertEqual(InheritanceHandlerIndex, SerializerInterfaces.IndexOf(typeof(ITypeInheritanceHandler<,>))); + DebugTools.AssertEqual(ValidatorIndex, SerializerInterfaces.IndexOf(typeof(ITypeValidator<,>))); + DebugTools.AssertEqual(CopyCreatorIndex, SerializerInterfaces.IndexOf(typeof(ITypeCopyCreator<>))); + DebugTools.AssertEqual(CopierIndex, SerializerInterfaces.IndexOf(typeof(ITypeCopier<>))); + + DebugTools.AssertEqual(MappingIndex, Nodes.IndexOf(typeof(MappingDataNode))); + DebugTools.AssertEqual(SequenceIndex, Nodes.IndexOf(typeof(SequenceDataNode))); + DebugTools.AssertEqual(ValueIndex, Nodes.IndexOf(typeof(ValueDataNode))); + _regularSerializerProvider = new(this, typeSerializers); } @@ -62,6 +118,10 @@ public sealed partial class SerializationManager ser.SerMan = this; ser.Log = _serializerSawmill; } + + if (result is IPostInjectInit postInject) + postInject.PostInject(); + return result; } @@ -102,7 +162,7 @@ public sealed partial class SerializationManager public sealed class SerializerProvider { - private SerializationManager _ser; + private readonly SerializationManager _ser; public SerializerProvider(ISerializationManager ser, IEnumerable typeSerializers) : this(ser) { @@ -122,8 +182,9 @@ public sealed partial class SerializationManager } } - private Dictionary> _typeNodeSerializers = new(); - private Dictionary> _typeSerializers = new(); + private (object? Regular, object? Generic, bool Init)[] _typeNodeSerializersArray = []; + private readonly ConcurrentDictionary> _typeNodeSerializers = new(); + private readonly ConcurrentDictionary> _typeSerializers = new(); // TODO make this a 1d array containing the 6 interfaces /// @@ -131,15 +192,15 @@ public sealed partial class SerializationManager /// that they serialize. /// for the first index. /// - private (object? Regular, object? Generic)[]?[] _typeSerializersArray = new (object? Regular, object? Generic)[]?[] { }; + private (object? Regular, object? Generic)[]?[] _typeSerializersArray = []; - private Dictionary> _genericTypeNodeSerializers = new(); - private Dictionary> _genericTypeSerializers = new(); + private readonly ConcurrentDictionary> _genericTypeNodeSerializers = new(); + private readonly ConcurrentDictionary> _genericTypeSerializers = new(); - private List _typeNodeInterfaces = new(); - private List _typeInterfaces = new(); + private readonly List _typeNodeInterfaces = new(); + private readonly List _typeInterfaces = new(); - private readonly object _lock = new(); + private readonly Lock _lock = new(); #region GetSerializerMethods @@ -148,11 +209,51 @@ public sealed partial class SerializationManager where TNode : DataNode { serializer = default; - if (!TryGetTypeNodeSerializer(typeof(TInterface).GetGenericTypeDefinition(), typeof(TType), typeof(TNode), out var rawSerializer)) - return false; + object? rawSerializer; + var index = TypeSerializerType.Index; + if (index < _typeNodeSerializersArray.Length) + { + ref var serializers = ref _typeNodeSerializersArray[index]; + if (serializers.Init) + { + if (serializers.Regular != null) + { + serializer = (TInterface) serializers.Regular; + return true; + } - serializer = (TInterface)rawSerializer; - return true; + if (serializers.Generic != null) + { + serializer = (TInterface) serializers.Generic; + return true; + } + + return false; + } + + if (TryGetTypeNodeSerializer(typeof(TInterface).GetGenericTypeDefinition(), + typeof(TType), + typeof(TNode), + out rawSerializer)) + { + serializer = (TInterface) rawSerializer; + return true; + } + + serializers.Init = true; + return false; + } + + if (TryGetTypeNodeSerializer(typeof(TInterface).GetGenericTypeDefinition(), + typeof(TType), + typeof(TNode), + out rawSerializer)) + { + serializer = (TInterface) rawSerializer; + return true; + } + + return false; } internal bool TryGetTypeNodeSerializerArray([NotNullWhen(true)] out TInterface? serializer) @@ -169,30 +270,28 @@ public sealed partial class SerializationManager public bool TryGetTypeNodeSerializer(Type interfaceType, Type objectType, Type nodeType, [NotNullWhen(true)] out object? serializer) { - lock (_lock) + if (_typeNodeSerializers.TryGetValue(interfaceType, out var typeNodeSerializers) && + typeNodeSerializers.TryGetValue((objectType, nodeType), out serializer)) + return true; + + if (_genericTypeNodeSerializers.TryGetValue(interfaceType, out var genericTypeNodeSerializers) && + objectType.IsGenericType) { - if (_typeNodeSerializers.TryGetValue(interfaceType, out var typeNodeSerializers) && - typeNodeSerializers.TryGetValue((objectType, nodeType), out serializer)) - return true; - - if (_genericTypeNodeSerializers.TryGetValue(interfaceType, out var genericTypeNodeSerializers) && - objectType.IsGenericType) + var typeDef = objectType.GetGenericTypeDefinition(); + foreach (var (key, val) in genericTypeNodeSerializers) { - var typeDef = objectType.GetGenericTypeDefinition(); - foreach (var (key, val) in genericTypeNodeSerializers) - { - if (typeDef.HasSameMetadataDefinitionAs(key.ObjectType) && nodeType == key.NodeType) - { - var serializerType = val.MakeGenericType(objectType.GetGenericArguments()); - serializer = RegisterSerializer(serializerType)!; - return true; - } - } - } + if (!typeDef.HasSameMetadataDefinitionAs(key.ObjectType) || nodeType != key.NodeType) + continue; - serializer = null; - return false; + var serializerType = val.MakeGenericType(objectType.GetGenericArguments()); + serializer = RegisterSerializer(serializerType)!; + RegisterIndexedNodeSerializer(interfaceType, objectType, key.NodeType, serializer, false); + return true; + } } + + serializer = null; + return false; } public TInterface GetTypeNodeSerializer() @@ -226,31 +325,34 @@ public sealed partial class SerializationManager public bool TryGetTypeSerializer(Type interfaceType, Type objectType, [NotNullWhen(true)] out object? serializer) { - lock (_lock) + if (_typeSerializers.TryGetValue(interfaceType, out var typeSerializers) && + typeSerializers.TryGetValue(objectType, out serializer)) + return true; + + if (_genericTypeSerializers.TryGetValue(interfaceType, out var genericTypeSerializers) && + objectType.IsGenericType) { - if (_typeSerializers.TryGetValue(interfaceType, out var typeSerializers) && - typeSerializers.TryGetValue(objectType, out serializer)) - return true; - - if (_genericTypeSerializers.TryGetValue(interfaceType, out var genericTypeSerializers) && - objectType.IsGenericType) + var typeDef = objectType.GetGenericTypeDefinition(); + foreach (var (key, val) in genericTypeSerializers) { - var typeDef = objectType.GetGenericTypeDefinition(); - foreach (var (key, val) in genericTypeSerializers) - { - if (typeDef.HasSameMetadataDefinitionAs(key)) - { - var serializerType = val.MakeGenericType(objectType.GetGenericArguments()); - serializer = RegisterSerializer(serializerType)!; - RegisterIndexedSerializer(objectType, SerializerInterfaces.IndexOf(interfaceType), serializer, false); - return true; - } - } - } + if (!typeDef.HasSameMetadataDefinitionAs(key)) + continue; - serializer = null; - return false; + var serializerType = val.MakeGenericType(objectType.GetGenericArguments()); + serializer = RegisterSerializer(serializerType)!; + RegisterIndexedSerializer( + objectType, + SerializerInterfaces.IndexOf(interfaceType), + serializer, + false + ); + + return true; + } } + + serializer = null; + return false; } internal bool TryGetCopierOrCreator(out ITypeCopier? copier, out ITypeCopyCreator? copyCreator) @@ -311,95 +413,100 @@ public sealed partial class SerializationManager private object RegisterSerializer(Type type, object obj) { - lock (_lock) + foreach (var @interface in type.GetInterfaces()) { - foreach (var @interface in type.GetInterfaces()) + if (!@interface.IsGenericType) continue; + + foreach (var typeInterface in _typeInterfaces) { - if (!@interface.IsGenericType) continue; + if (!@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) + continue; - for (var i = 0; i < _typeInterfaces.Count; i++) - { - var typeInterface = _typeInterfaces[i]; - if (@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) - { - var arguments = @interface.GetGenericArguments(); - if (arguments.Length != 1) - throw new InvalidGenericParameterCountException(); - _typeSerializers.GetOrNew(typeInterface).Add(arguments[0], obj); - RegisterIndexedSerializer(arguments[0], SerializerInterfaces.IndexOf(typeInterface), obj, true); - } - } + var arguments = @interface.GetGenericArguments(); + if (arguments.Length != 1) + throw new InvalidGenericParameterCountException(); - foreach (var typeInterface in _typeNodeInterfaces) - { - if (@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) - { - var arguments = @interface.GetGenericArguments(); - if (arguments.Length != 2) - throw new InvalidGenericParameterCountException(); - _typeNodeSerializers.GetOrNew(typeInterface).Add((arguments[0], arguments[1]), obj); - } - } + _typeSerializers.GetOrNew(typeInterface).TryAdd(arguments[0], obj); + RegisterIndexedSerializer( + arguments[0], + SerializerInterfaces.IndexOf(typeInterface), + obj, + true + ); } - return obj; + foreach (var typeInterface in _typeNodeInterfaces) + { + if (!@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) + continue; + + var arguments = @interface.GetGenericArguments(); + if (arguments.Length != 2) + throw new InvalidGenericParameterCountException(); + + _typeNodeSerializers.GetOrAdd(typeInterface, _ => new()) + .TryAdd((arguments[0], arguments[1]), obj); + RegisterIndexedNodeSerializer( + typeInterface, + arguments[0], + arguments[1], + obj, + true + ); + } } + + return obj; } public T? RegisterSerializer() => (T?)RegisterSerializer(typeof(T)); public object? RegisterSerializer(Type type) { - lock (_lock) + if (!type.IsGenericTypeDefinition) + return RegisterSerializer(type, _ser.CreateSerializer(type)); + + var typeArguments = type.GetGenericArguments(); + foreach (var @interface in type.GetInterfaces()) { - if (type.IsGenericTypeDefinition) + foreach (var typeInterface in _typeInterfaces) { - var typeArguments = type.GetGenericArguments(); - foreach (var @interface in type.GetInterfaces()) + if (!@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) + continue; + + var arguments = @interface.GetGenericArguments(); + if (arguments.Length != 1) + throw new InvalidGenericParameterCountException(); + var objArguments = arguments[0].GetGenericArguments(); + for (var i = 0; i < typeArguments.Length; i++) { - foreach (var typeInterface in _typeInterfaces) - { - if (@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) - { - var arguments = @interface.GetGenericArguments(); - if (arguments.Length != 1) - throw new InvalidGenericParameterCountException(); - var objArguments = arguments[0].GetGenericArguments(); - for (int i = 0; i < typeArguments.Length; i++) - { - if (typeArguments[i] != objArguments[i]) - throw new GenericParameterMismatchException(); - } - - _genericTypeSerializers.GetOrNew(typeInterface).Add(arguments[0], type); - } - } - - foreach (var typeInterface in _typeNodeInterfaces) - { - if (@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) - { - var arguments = @interface.GetGenericArguments(); - if (arguments.Length != 2) - throw new InvalidGenericParameterCountException(); - var objArguments = arguments[0].GetGenericArguments(); - for (int i = 0; i < typeArguments.Length; i++) - { - if (typeArguments[i] != objArguments[i]) - throw new GenericParameterMismatchException(); - } - - _genericTypeNodeSerializers.GetOrNew(typeInterface) - .Add((arguments[0], arguments[1]), type); - } - } + if (typeArguments[i] != objArguments[i]) + throw new GenericParameterMismatchException(); } - return null; + _genericTypeSerializers.GetOrNew(typeInterface).TryAdd(arguments[0], type); } - return RegisterSerializer(type, _ser.CreateSerializer(type)); + foreach (var typeInterface in _typeNodeInterfaces) + { + if (!@interface.GetGenericTypeDefinition().HasSameMetadataDefinitionAs(typeInterface)) + continue; + + var arguments = @interface.GetGenericArguments(); + if (arguments.Length != 2) + throw new InvalidGenericParameterCountException(); + var objArguments = arguments[0].GetGenericArguments(); + for (var i = 0; i < typeArguments.Length; i++) + { + if (typeArguments[i] != objArguments[i]) + throw new GenericParameterMismatchException(); + } + + _genericTypeNodeSerializers.GetOrNew(typeInterface).TryAdd((arguments[0], arguments[1]), type); + } } + + return null; } //todo paul serv3 is there a better way than comparing names here? @@ -420,7 +527,7 @@ public sealed partial class SerializationManager if (genericInterface.HasSameMetadataDefinitionAs(genericTypeNode)) { var genericInterfaceParams = genericInterface.GetGenericArguments(); - for (int i = 0; i < genericParams.Length; i++) + for (var i = 0; i < genericParams.Length; i++) { if (genericParams[i].Name != genericInterfaceParams[i].Name) throw new GenericParameterMismatchException(); @@ -431,7 +538,7 @@ public sealed partial class SerializationManager else if (genericInterface.HasSameMetadataDefinitionAs(genericType)) { var genericInterfaceParams = genericInterface.GetGenericArguments(); - for (int i = 0; i < genericParams.Length; i++) + for (var i = 0; i < genericParams.Length; i++) { if (genericParams[i].Name != genericInterfaceParams[i].Name) throw new GenericParameterMismatchException(); @@ -447,9 +554,7 @@ public sealed partial class SerializationManager { var id = SerializedType.GetId(elementType); if (id >= _typeSerializersArray.Length) - { Array.Resize(ref _typeSerializersArray, (id + 1) * 2); - } var array = _typeSerializersArray[id]; if (array == null) @@ -459,12 +564,26 @@ public sealed partial class SerializationManager } if (regular) - { array[interfaceIndex].Regular = serializer; - } else - { array[interfaceIndex].Generic = serializer; + } + + private void RegisterIndexedNodeSerializer(Type interfaceIndex, Type elementType, Type nodeType, object serializer, bool regular) + { + lock (_lock) + { + var id = TypeSerializerType.GetId(interfaceIndex, elementType, nodeType); + if (id >= _typeNodeSerializersArray.Length) + Array.Resize(ref _typeNodeSerializersArray, (id + 1) * 2); + + ref var tuple = ref _typeNodeSerializersArray[id]; + if (regular) + tuple.Regular = serializer; + else + tuple.Generic = serializer; + + tuple.Init = true; } } @@ -474,7 +593,7 @@ public sealed partial class SerializationManager private static class SerializedType { internal static int Id; - private static readonly object Lock = new(); + private static readonly Lock Lock = new(); internal static int GetId(Type type) { @@ -515,4 +634,32 @@ public sealed partial class SerializationManager SerializationGenerated = serializationGenerated; } } + + internal static class TypeSerializerType + { + internal static int GetId(Type typeInterface, Type type, Type typeNode) + { + var interfaceIndex = SerializerInterfaces.IndexOf(typeInterface.GetGenericTypeDefinition()); + if (interfaceIndex == -1) + throw new ArgumentException($"Invalid type interface: {typeInterface}"); + + var nodeIndex = Nodes.IndexOf(typeNode); + if (nodeIndex == -1) + throw new ArgumentException($"Invalid node type: {typeInterface}"); + + return SerializedType.GetId(type) * + (SerializerInterfaces.Length + Nodes.Length) + + interfaceIndex + + nodeIndex; + } + } + + internal static class TypeSerializerType + { + // ReSharper disable once StaticMemberInGenericType + internal static readonly int Index = SerializedType.Information.Id * + (SerializerInterfaces.Length + Nodes.Length) + + SerializerInterfaces.IndexOf(typeof(TInterface).GetGenericTypeDefinition()) + + Nodes.IndexOf(typeof(TNode)); + } } diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.cs b/Robust.Shared/Serialization/Manager/SerializationManager.cs index a09bbd5e4a..0a6b846230 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.cs @@ -371,17 +371,17 @@ namespace Robust.Shared.Serialization.Manager } #pragma warning disable CS0618 - private static void RunAfterHook(TValue instance, SerializationHookContext ctx) - { - if (instance is ISerializationHooks hooks) - RunAfterHookGenerated(hooks, ctx); - } - - private static void RunAfterHookGenerated(TValue instance, SerializationHookContext ctx) where TValue : ISerializationHooks + internal static void TryRunAfterHook(TValue instance, SerializationHookContext ctx) { if (ctx.SkipHooks) return; + if (instance is ISerializationHooks hooks) + ForceRunAfterHookGenerated(hooks, ctx); + } + + private static void ForceRunAfterHookGenerated(TValue instance, SerializationHookContext ctx) where TValue : ISerializationHooks + { DebugTools.Assert(!typeof(TValue).IsValueType, "ISerializationHooks must only be used on reference types"); if (ctx.DeferQueue != null)