// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; using MessagePack; using MessagePack.Formatters; using MessagePack.Resolvers; namespace osu.Game.Online { /// <summary> /// Handles SignalR being unable to comprehend [Union] types correctly by redirecting to a known base (union) type. /// See https://github.com/dotnet/aspnetcore/issues/7298. /// </summary> public class SignalRUnionWorkaroundResolver : IFormatterResolver { public static readonly MessagePackSerializerOptions OPTIONS = MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); private static readonly IReadOnlyDictionary<Type, IMessagePackFormatter> formatter_map = createFormatterMap(); private static IReadOnlyDictionary<Type, IMessagePackFormatter> createFormatterMap() { IEnumerable<(Type derivedType, Type baseType)> baseMap = SignalRWorkaroundTypes.BASE_TYPE_MAPPING; // This should not be required. The fallback should work. But something is weird with the way caching is done. // For future adventurers, I would not advise looking into this further. It's likely not worth the effort. baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)).Distinct()); return new Dictionary<Type, IMessagePackFormatter>(baseMap.Select(t => { var formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(TypeRedirectingFormatter<,>).MakeGenericType(t.derivedType, t.baseType)); return new KeyValuePair<Type, IMessagePackFormatter>(t.derivedType, formatter); })); } public IMessagePackFormatter<T> GetFormatter<T>() { if (formatter_map.TryGetValue(typeof(T), out var formatter)) return (IMessagePackFormatter<T>)formatter; return StandardResolver.Instance.GetFormatter<T>(); } public class TypeRedirectingFormatter<TActual, TBase> : IMessagePackFormatter<TActual> { private readonly IMessagePackFormatter<TBase> baseFormatter; public TypeRedirectingFormatter() { baseFormatter = StandardResolver.Instance.GetFormatter<TBase>(); } public void Serialize(ref MessagePackWriter writer, TActual value, MessagePackSerializerOptions options) => baseFormatter.Serialize(ref writer, (TBase)(object)value, StandardResolver.Options); public TActual Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => (TActual)(object)baseFormatter.Deserialize(ref reader, StandardResolver.Options); } } }