mirror of
https://github.com/ppy/osu.git
synced 2024-11-06 09:07:25 +08: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:
commit
3f37d4395f
@ -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)
|
||||
builder.AddMessagePackProtocol();
|
||||
{
|
||||
builder.AddMessagePackProtocol(options =>
|
||||
{
|
||||
options.SerializerOptions = SignalRUnionWorkaroundResolver.OPTIONS;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// eventually we will precompile resolvers for messagepack, but this isn't working currently
|
||||
|
@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(TeamVersusRoomState))]
|
||||
// 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.
|
||||
public class MatchRoomState
|
||||
[Union(0, typeof(TeamVersusRoomState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||
public abstract class MatchRoomState
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
@ -11,6 +12,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
public abstract class MatchUserRequest
|
||||
{
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
[Union(0, typeof(TeamVersusUserState))]
|
||||
// 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.
|
||||
public class MatchUserState
|
||||
[Union(0, typeof(TeamVersusUserState))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||
// TODO: abstract breaks json serialisation. attention will be required for iOS support (unless we get messagepack AOT working instead).
|
||||
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.
|
||||
// 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)
|
||||
{
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user