// Copyright (c) ppy Pty Ltd . 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 { /// /// 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. /// public class SignalRUnionWorkaroundResolver : IFormatterResolver { public static readonly MessagePackSerializerOptions OPTIONS = MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); private static readonly IReadOnlyDictionary formatter_map = createFormatterMap(); private static IReadOnlyDictionary 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(baseMap.Select(t => { var formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(TypeRedirectingFormatter<,>).MakeGenericType(t.derivedType, t.baseType)); return new KeyValuePair(t.derivedType, formatter); })); } public IMessagePackFormatter GetFormatter() { if (formatter_map.TryGetValue(typeof(T), out var formatter)) return (IMessagePackFormatter)formatter; return StandardResolver.Instance.GetFormatter(); } public class TypeRedirectingFormatter : IMessagePackFormatter { private readonly IMessagePackFormatter baseFormatter; public TypeRedirectingFormatter() { baseFormatter = StandardResolver.Instance.GetFormatter(); } 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); } } }