mirror of
https://github.com/osukey/osukey.git
synced 2025-08-08 09:03:50 +09:00
Merge pull request #14389 from peppy/fix-messagepack-union
Fix android not being able to connect to multiplayer server
This commit is contained in:
@ -0,0 +1,72 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using MessagePack;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Online;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Online
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestMultiplayerMessagePackSerialization
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSerialiseRoom()
|
||||||
|
{
|
||||||
|
var room = new MultiplayerRoom(1)
|
||||||
|
{
|
||||||
|
MatchState = new TeamVersusRoomState()
|
||||||
|
};
|
||||||
|
|
||||||
|
var serialized = MessagePackSerializer.Serialize(room);
|
||||||
|
|
||||||
|
var deserialized = MessagePackSerializer.Deserialize<MultiplayerRoom>(serialized);
|
||||||
|
|
||||||
|
Assert.IsTrue(deserialized.MatchState is TeamVersusRoomState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSerialiseUserStateExpected()
|
||||||
|
{
|
||||||
|
var state = new TeamVersusUserState();
|
||||||
|
|
||||||
|
var serialized = MessagePackSerializer.Serialize(typeof(MatchUserState), state);
|
||||||
|
var deserialized = MessagePackSerializer.Deserialize<MatchUserState>(serialized);
|
||||||
|
|
||||||
|
Assert.IsTrue(deserialized is TeamVersusUserState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSerialiseUnionFailsWithSingalR()
|
||||||
|
{
|
||||||
|
var state = new TeamVersusUserState();
|
||||||
|
|
||||||
|
// SignalR serialises using the actual type, rather than a base specification.
|
||||||
|
var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state);
|
||||||
|
|
||||||
|
// works with explicit type specified.
|
||||||
|
MessagePackSerializer.Deserialize<TeamVersusUserState>(serialized);
|
||||||
|
|
||||||
|
// fails with base (union) type.
|
||||||
|
Assert.Throws<MessagePackSerializationException>(() => MessagePackSerializer.Deserialize<MatchUserState>(serialized));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSerialiseUnionSucceedsWithWorkaround()
|
||||||
|
{
|
||||||
|
var state = new TeamVersusUserState();
|
||||||
|
|
||||||
|
// SignalR serialises using the actual type, rather than a base specification.
|
||||||
|
var serialized = MessagePackSerializer.Serialize(typeof(TeamVersusUserState), state, SignalRUnionWorkaroundResolver.OPTIONS);
|
||||||
|
|
||||||
|
// works with explicit type specified.
|
||||||
|
MessagePackSerializer.Deserialize<TeamVersusUserState>(serialized);
|
||||||
|
|
||||||
|
// works with custom resolver.
|
||||||
|
var deserialized = MessagePackSerializer.Deserialize<MatchUserState>(serialized, SignalRUnionWorkaroundResolver.OPTIONS);
|
||||||
|
Assert.IsTrue(deserialized is TeamVersusUserState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -148,7 +148,12 @@ namespace osu.Game.Online
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (RuntimeInfo.SupportsJIT && preferMessagePack)
|
if (RuntimeInfo.SupportsJIT && preferMessagePack)
|
||||||
builder.AddMessagePackProtocol();
|
{
|
||||||
|
builder.AddMessagePackProtocol(options =>
|
||||||
|
{
|
||||||
|
options.SerializerOptions = SignalRUnionWorkaroundResolver.OPTIONS;
|
||||||
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// eventually we will precompile resolvers for messagepack, but this isn't working currently
|
// eventually we will precompile resolvers for messagepack, but this isn't working currently
|
||||||
|
@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
[Union(0, typeof(TeamVersusRoomState))]
|
[Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||||
// TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation.
|
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||||
public class MatchRoomState
|
public abstract class MatchRoomState
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer
|
namespace osu.Game.Online.Multiplayer
|
||||||
{
|
{
|
||||||
@ -11,6 +12,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
|
[Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||||
public abstract class MatchUserRequest
|
public abstract class MatchUserRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
[Union(0, typeof(TeamVersusUserState))]
|
[Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||||
// TODO: this will need to be abstract or interface when/if we get messagepack working. for now it isn't as it breaks json serialisation.
|
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||||
public class MatchUserState
|
public abstract class MatchUserState
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
// Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization.
|
// Importantly, we are intentionally not using MessagePack here to correctly support derived class serialization.
|
||||||
// More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code.
|
// More information on the limitations / reasoning can be found in osu-server-spectator's initialisation code.
|
||||||
connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint, false);
|
connector = api.GetHubConnector(nameof(OnlineMultiplayerClient), endpoint);
|
||||||
|
|
||||||
if (connector != null)
|
if (connector != null)
|
||||||
{
|
{
|
||||||
|
61
osu.Game/Online/SignalRUnionWorkaroundResolver.cs
Normal file
61
osu.Game/Online/SignalRUnionWorkaroundResolver.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MessagePack;
|
||||||
|
using MessagePack.Formatters;
|
||||||
|
using MessagePack.Resolvers;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
|
|
||||||
|
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 Dictionary<Type, IMessagePackFormatter> formatter_map = new Dictionary<Type, IMessagePackFormatter>
|
||||||
|
{
|
||||||
|
{ typeof(TeamVersusUserState), new TypeRedirectingFormatter<TeamVersusUserState, MatchUserState>() },
|
||||||
|
{ typeof(TeamVersusRoomState), new TypeRedirectingFormatter<TeamVersusRoomState, MatchRoomState>() },
|
||||||
|
{ typeof(ChangeTeamRequest), new TypeRedirectingFormatter<ChangeTeamRequest, MatchUserRequest>() },
|
||||||
|
|
||||||
|
// These 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.
|
||||||
|
{ typeof(MatchUserState), new TypeRedirectingFormatter<MatchUserState, MatchUserState>() },
|
||||||
|
{ typeof(MatchRoomState), new TypeRedirectingFormatter<MatchRoomState, MatchRoomState>() },
|
||||||
|
{ typeof(MatchUserRequest), new TypeRedirectingFormatter<MatchUserRequest, MatchUserRequest>() },
|
||||||
|
{ typeof(MatchServerEvent), new TypeRedirectingFormatter<MatchServerEvent, MatchServerEvent>() },
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user