From fbf8bc2e2a99fa6229e796829e17d0f92fa92234 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Sep 2025 12:37:43 +0900 Subject: [PATCH 1/3] Refactor multiplayer room initialisation --- .../MatchTypeRoomEventHookTests.cs | 43 +------ .../TeamVersusMatchControllerTests.cs | 66 +---------- .../Multiplayer/IMultiplayerHubContext.cs | 6 + .../Hubs/Multiplayer/MultiplayerHub.cs | 107 ++---------------- .../Hubs/Multiplayer/MultiplayerHubContext.cs | 16 +-- .../Hubs/Multiplayer/ServerMultiplayerRoom.cs | 52 ++++++++- 6 files changed, 82 insertions(+), 208 deletions(-) diff --git a/osu.Server.Spectator.Tests/Multiplayer/MatchTypeRoomEventHookTests.cs b/osu.Server.Spectator.Tests/Multiplayer/MatchTypeRoomEventHookTests.cs index 68f64f74..8ee1b748 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/MatchTypeRoomEventHookTests.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/MatchTypeRoomEventHookTests.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Moq; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Server.Spectator.Hubs.Multiplayer; using Xunit; @@ -19,19 +18,7 @@ public class MatchTypeRoomEventHookTests : MultiplayerTest public async Task NewUserJoinedTriggersRulesetHook() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); Mock controller = new Mock(); await room.ChangeMatchType(controller.Object); @@ -45,19 +32,7 @@ public async Task NewUserJoinedTriggersRulesetHook() public async Task UserLeavesTriggersRulesetHook() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); var user = new MultiplayerRoomUser(1); @@ -74,19 +49,7 @@ public async Task UserLeavesTriggersRulesetHook() public async Task TypeChangeTriggersInitialJoins() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); // join a number of users initially to the room for (int i = 0; i < 5; i++) diff --git a/osu.Server.Spectator.Tests/Multiplayer/TeamVersusMatchControllerTests.cs b/osu.Server.Spectator.Tests/Multiplayer/TeamVersusMatchControllerTests.cs index 01c6fa14..5d7a29bc 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/TeamVersusMatchControllerTests.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/TeamVersusMatchControllerTests.cs @@ -20,18 +20,7 @@ public class TeamVersusMatchControllerTests : MultiplayerTest public async Task UserRequestsValidTeamChange(int team) { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); var teamVersus = new TeamVersusMatchController(room, hub.Object, DatabaseFactory.Object); @@ -56,18 +45,8 @@ public async Task UserRequestsValidTeamChange(int team) public async Task UserRequestsInvalidTeamChange(int team) { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); + var teamVersus = new TeamVersusMatchController(room, hub.Object, DatabaseFactory.Object); // change the match type @@ -92,18 +71,7 @@ public async Task UserRequestsInvalidTeamChange(int team) public async Task NewUsersAssignedToTeamWithFewerUsers() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); // change the match type await room.ChangeMatchType(MatchType.TeamVersus); @@ -133,18 +101,7 @@ public async Task NewUsersAssignedToTeamWithFewerUsers() public async Task InitialUsersAssignedToTeamsEqually() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); // join a number of users initially to the room for (int i = 0; i < 5; i++) @@ -164,18 +121,7 @@ public async Task InitialUsersAssignedToTeamsEqually() public async Task StateMaintainedBetweenRulesetSwitch() { var hub = new Mock(); - var room = new ServerMultiplayerRoom(1, hub.Object, DatabaseFactory.Object) - { - Playlist = - { - new MultiplayerPlaylistItem - { - BeatmapID = 3333, - BeatmapChecksum = "3333" - }, - } - }; - await room.Initialise(); + var room = await ServerMultiplayerRoom.InitialiseAsync(ROOM_ID, hub.Object, DatabaseFactory.Object); await room.ChangeMatchType(MatchType.TeamVersus); diff --git a/osu.Server.Spectator/Hubs/Multiplayer/IMultiplayerHubContext.cs b/osu.Server.Spectator/Hubs/Multiplayer/IMultiplayerHubContext.cs index 2ff13bc1..87cf2986 100644 --- a/osu.Server.Spectator/Hubs/Multiplayer/IMultiplayerHubContext.cs +++ b/osu.Server.Spectator/Hubs/Multiplayer/IMultiplayerHubContext.cs @@ -1,8 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -147,5 +149,9 @@ public interface IMultiplayerHubContext Task NotifyMatchmakingItemSelected(ServerMultiplayerRoom room, int userId, long playlistItemId); Task NotifyMatchmakingItemDeselected(ServerMultiplayerRoom room, int userId, long playlistItemId); + + void Log(ServerMultiplayerRoom room, MultiplayerRoomUser? user, string message, LogLevel logLevel = LogLevel.Information); + + void Error(MultiplayerRoomUser? user, string message, Exception exception); } } diff --git a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs index 6abef40d..f4be3e29 100644 --- a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs +++ b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs @@ -14,7 +14,6 @@ using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; using osu.Server.Spectator.Database; -using osu.Server.Spectator.Database.Models; using osu.Server.Spectator.Entities; using osu.Server.Spectator.Extensions; using osu.Server.Spectator.Hubs.Multiplayer.Matchmaking.Queue; @@ -91,52 +90,29 @@ public async Task JoinRoomWithPassword(long roomId, string pass // add the user to the room. var roomUser = new MultiplayerRoomUser(Context.GetUserId()); - // track whether this join necessitated starting the process of fetching the room and adding it to the room store. - bool newRoomFetchStarted = false; - using (var roomUsage = await Rooms.GetForUse(roomId, true)) { + // track whether this join necessitated starting the process of fetching the room and adding it to the room store. + bool newRoomFetchStarted = roomUsage.Item == null; ServerMultiplayerRoom? room = null; try { - if (roomUsage.Item == null) - { - newRoomFetchStarted = true; - - // the requested room is not yet tracked by this server. - room = await retrieveRoom(roomId); - - if (!string.IsNullOrEmpty(room.Settings.Password)) - { - if (room.Settings.Password != password) - throw new InvalidPasswordException(); - } - - // the above call will only succeed if this user is the host. - room.Host = roomUser; + room = roomUsage.Item ??= await ServerMultiplayerRoom.InitialiseAsync(roomId, HubContext, databaseFactory); - // mark the room active - and wait for confirmation of this operation from the database - before adding the user to the room. - await markRoomActive(room); + // this is a sanity check to keep *rooms* in a good state. + // in theory the connection clean-up code should handle this correctly. + if (room.Users.Any(u => u.UserID == roomUser.UserID)) + throw new InvalidOperationException($"User {roomUser.UserID} attempted to join room {room.RoomID} they are already present in."); - roomUsage.Item = room; - } - else + if (!string.IsNullOrEmpty(room.Settings.Password)) { - room = roomUsage.Item; - - // this is a sanity check to keep *rooms* in a good state. - // in theory the connection clean-up code should handle this correctly. - if (room.Users.Any(u => u.UserID == roomUser.UserID)) - throw new InvalidOperationException($"User {roomUser.UserID} attempted to join room {room.RoomID} they are already present in."); - - if (!string.IsNullOrEmpty(room.Settings.Password)) - { - if (room.Settings.Password != password) - throw new InvalidPasswordException(); - } + if (room.Settings.Password != password) + throw new InvalidPasswordException(); } + room.Host ??= roomUser; + userUsage.Item = new MultiplayerClientState(Context.ConnectionId, Context.GetUserId(), roomId); // because match controllers may send subsequent information via Users collection hooks, @@ -201,65 +177,6 @@ public async Task JoinRoomWithPassword(long roomId, string pass return MessagePackSerializer.Deserialize(roomBytes); } - /// - /// Attempt to retrieve and construct a room from the database backend, based on a room ID specification. - /// This will check the database backing to ensure things are in a consistent state. - /// It should only be called by the room's host, before any other user has joined (and will throw if not). - /// - /// The proposed room ID. - /// If anything is wrong with this request. - private async Task retrieveRoom(long roomId) - { - Log($"Retrieving room {roomId} from database"); - - using (var db = databaseFactory.GetInstance()) - { - // TODO: this call should be transactional, and mark the room as managed by this server instance. - // This will allow for other instances to know not to reinitialise the room if the host arrives there. - // Alternatively, we can move lobby retrieval away from osu-web and not require this in the first place. - // Needs further discussion and consideration either way. - var databaseRoom = await db.GetRealtimeRoomAsync(roomId); - - if (databaseRoom == null) - throw new InvalidOperationException("Specified match does not exist."); - - if (databaseRoom.ends_at != null && databaseRoom.ends_at < DateTimeOffset.Now) - throw new InvalidStateException("Match has already ended."); - - if (databaseRoom.type != database_match_type.matchmaking && databaseRoom.user_id != Context.GetUserId()) - throw new InvalidOperationException("Non-host is attempting to join match before host"); - - var room = new ServerMultiplayerRoom(roomId, HubContext, databaseFactory) - { - ChannelID = databaseRoom.channel_id, - Settings = new MultiplayerRoomSettings - { - Name = databaseRoom.name, - Password = databaseRoom.password, - MatchType = databaseRoom.type.ToMatchType(), - QueueMode = databaseRoom.queue_mode.ToQueueMode(), - AutoStartDuration = TimeSpan.FromSeconds(databaseRoom.auto_start_duration), - AutoSkip = databaseRoom.auto_skip - } - }; - - await room.Initialise(); - - return room; - } - } - - /// - /// Marks a room active at the database, implying the host has joined and this server is now in control of the room's lifetime. - /// - private async Task markRoomActive(ServerMultiplayerRoom room) - { - Log(room, "Host marking room active"); - - using (var db = databaseFactory.GetInstance()) - await db.MarkRoomActiveAsync(room); - } - public async Task LeaveRoom() { Log("Requesting to leave room"); diff --git a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHubContext.cs b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHubContext.cs index f52541f5..437f8930 100644 --- a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHubContext.cs +++ b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHubContext.cs @@ -108,14 +108,14 @@ public async Task NotifySettingsChanged(ServerMultiplayerRoom room, bool playlis public async Task UnreadyAllUsers(ServerMultiplayerRoom room, bool resetBeatmapAvailability) { - log(room, null, "Unreadying all users"); + Log(room, null, "Unreadying all users"); foreach (var u in room.Users.Where(u => u.State == MultiplayerUserState.Ready).ToArray()) await ChangeAndBroadcastUserState(room, u, MultiplayerUserState.Idle); if (resetBeatmapAvailability) { - log(room, null, "Resetting all users' beatmap availability"); + Log(room, null, "Resetting all users' beatmap availability"); foreach (var user in room.Users) await ChangeAndBroadcastUserBeatmapAvailability(room, user, new BeatmapAvailability(DownloadState.Unknown)); @@ -178,7 +178,7 @@ public async Task ChangeUserStyle(int? beatmapId, int? rulesetId, ServerMultipla if (user.BeatmapId == beatmapId && user.RulesetId == rulesetId) return; - log(room, user, $"User style changing from (b:{user.BeatmapId}, r:{user.RulesetId}) to (b:{beatmapId}, r:{rulesetId})"); + Log(room, user, $"User style changing from (b:{user.BeatmapId}, r:{user.RulesetId}) to (b:{beatmapId}, r:{rulesetId})"); if (rulesetId < 0 || rulesetId > ILegacyRuleset.MAX_LEGACY_RULESET_ID) throw new InvalidStateException("Attempted to select an unsupported ruleset."); @@ -233,7 +233,7 @@ public async Task ChangeUserMods(IEnumerable newMods, ServerMultiplayerR public async Task ChangeAndBroadcastUserState(ServerMultiplayerRoom room, MultiplayerRoomUser user, MultiplayerUserState state) { - log(room, user, $"User state changed from {user.State} to {state}"); + Log(room, user, $"User state changed from {user.State} to {state}"); user.State = state; @@ -254,7 +254,7 @@ public async Task ChangeAndBroadcastUserBeatmapAvailability(ServerMultiplayerRoo public async Task ChangeRoomState(ServerMultiplayerRoom room, MultiplayerRoomState newState) { - log(room, null, $"Room state changing from {room.State} to {newState}"); + Log(room, null, $"Room state changing from {room.State} to {newState}"); room.State = newState; using (var db = databaseFactory.GetInstance()) @@ -325,7 +325,7 @@ public async Task StartOrStopGameplay(ServerMultiplayerRoom room) { await ChangeAndBroadcastUserState(room, user, MultiplayerUserState.Idle); await context.Clients.Client(connectionId).SendAsync(nameof(IMultiplayerClient.GameplayAborted), GameplayAbortReason.LoadTookTooLong); - log(room, user, "Gameplay aborted because this user took too long to load."); + Log(room, user, "Gameplay aborted because this user took too long to load."); } } @@ -400,7 +400,7 @@ public async Task NotifyMatchmakingItemDeselected(ServerMultiplayerRoom room, in await context.Clients.Group(MultiplayerHub.GetGroupId(room.RoomID)).SendAsync(nameof(IMatchmakingClient.MatchmakingItemDeselected), userId, playlistItemId); } - private void log(ServerMultiplayerRoom room, MultiplayerRoomUser? user, string message, LogLevel logLevel = LogLevel.Information) + public void Log(ServerMultiplayerRoom room, MultiplayerRoomUser? user, string message, LogLevel logLevel = LogLevel.Information) { logger.Log(logLevel, "[user:{userId}] [room:{roomID}] {message}", getLoggableUserIdentifier(user), @@ -408,7 +408,7 @@ private void log(ServerMultiplayerRoom room, MultiplayerRoomUser? user, string m message.Trim()); } - private void error(MultiplayerRoomUser? user, string message, Exception exception) + public void Error(MultiplayerRoomUser? user, string message, Exception exception) { logger.LogError(exception, "[user:{userId}] {message}", getLoggableUserIdentifier(user), diff --git a/osu.Server.Spectator/Hubs/Multiplayer/ServerMultiplayerRoom.cs b/osu.Server.Spectator/Hubs/Multiplayer/ServerMultiplayerRoom.cs index 3612ccd6..d098d67e 100644 --- a/osu.Server.Spectator/Hubs/Multiplayer/ServerMultiplayerRoom.cs +++ b/osu.Server.Spectator/Hubs/Multiplayer/ServerMultiplayerRoom.cs @@ -12,6 +12,7 @@ using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; using osu.Server.Spectator.Database; +using osu.Server.Spectator.Database.Models; using osu.Server.Spectator.Hubs.Multiplayer.Matchmaking; using osu.Server.Spectator.Hubs.Multiplayer.Standard; @@ -29,22 +30,63 @@ public IMatchController Controller private readonly IDatabaseFactory dbFactory; private IMatchController? matchController; - public ServerMultiplayerRoom(long roomId, IMultiplayerHubContext hub, IDatabaseFactory dbFactory) + private ServerMultiplayerRoom(long roomId, IMultiplayerHubContext hub, IDatabaseFactory dbFactory) : base(roomId) { this.hub = hub; this.dbFactory = dbFactory; } - public async Task Initialise() + /// + /// Attempt to retrieve and construct a room from the database backend, based on a room ID specification. + /// This will check the database backing to ensure things are in a consistent state. + /// This will also mark the room as active, indicating that this server is now in control of the room's lifetime. + /// + /// The room identifier. + /// The multiplayer hub context. + /// The database factory. + /// If the room does not exist in the database. + /// If the match has already ended. + public static async Task InitialiseAsync(long roomId, IMultiplayerHubContext hub, IDatabaseFactory dbFactory) { + ServerMultiplayerRoom room = new ServerMultiplayerRoom(roomId, hub, dbFactory); + + // TODO: this call should be transactional, and mark the room as managed by this server instance. + // This will allow for other instances to know not to reinitialise the room if the host arrives there. + // Alternatively, we can move lobby retrieval away from osu-web and not require this in the first place. + // Needs further discussion and consideration either way. using (var db = dbFactory.GetInstance()) { - foreach (var item in await db.GetAllPlaylistItemsAsync(RoomID)) - Playlist.Add(item.ToMultiplayerPlaylistItem()); + hub.Log(room, null, $"Retrieving room {roomId} from database"); + var databaseRoom = await db.GetRealtimeRoomAsync(roomId); + + if (databaseRoom == null) + throw new InvalidOperationException("Specified match does not exist."); + + if (databaseRoom.ends_at != null && databaseRoom.ends_at < DateTimeOffset.Now) + throw new InvalidStateException("Match has already ended."); + + room.ChannelID = databaseRoom.channel_id; + room.Settings = new MultiplayerRoomSettings + { + Name = databaseRoom.name, + Password = databaseRoom.password, + MatchType = databaseRoom.type.ToMatchType(), + QueueMode = databaseRoom.queue_mode.ToQueueMode(), + AutoStartDuration = TimeSpan.FromSeconds(databaseRoom.auto_start_duration), + AutoSkip = databaseRoom.auto_skip + }; + + foreach (var item in await db.GetAllPlaylistItemsAsync(roomId)) + room.Playlist.Add(item.ToMultiplayerPlaylistItem()); + + await room.ChangeMatchType(room.Settings.MatchType); + + hub.Log(room, null, "Marking room active"); + await db.MarkRoomActiveAsync(room); } - await ChangeMatchType(Settings.MatchType); + return room; } /// From c95e3c803d7cce795c1111de0e3f64c3cbb6fe4c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Sep 2025 13:27:27 +0900 Subject: [PATCH 2/3] Remove no longer valid test --- .../Multiplayer/RoomParticipationTests.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Server.Spectator.Tests/Multiplayer/RoomParticipationTests.cs b/osu.Server.Spectator.Tests/Multiplayer/RoomParticipationTests.cs index 7cef31fb..fc9879e0 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/RoomParticipationTests.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/RoomParticipationTests.cs @@ -165,25 +165,6 @@ public async Task UserJoinLeaveNotifiesOtherUsers() Receiver.Verify(r => r.UserLeft(roomUser), Times.Exactly(2)); } - [Fact] - public async Task UserJoinPreRetrievalFailureCleansUpRoom() - { - Database.Setup(db => db.GetRealtimeRoomAsync(ROOM_ID)) - .Callback(InitialiseRoom) - .ReturnsAsync(() => new multiplayer_room - { - type = database_match_type.head_to_head, - ends_at = DateTimeOffset.Now.AddMinutes(5), - user_id = USER_ID, - }); - - SetUserContext(ContextUser2); // not the correct user to join the game first; triggers host mismatch failure. - await Assert.ThrowsAnyAsync(() => Hub.JoinRoom(ROOM_ID)); - - await Assert.ThrowsAsync(() => Rooms.GetForUse(ROOM_ID)); - await Assert.ThrowsAsync(() => UserStates.GetForUse(USER_ID)); - } - [Fact] public async Task UserJoinPreJoinFailureCleansUpRoom() { From 53c20efb598dfa179036f9757c6fb19d468e76ac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Sep 2025 12:54:17 +0900 Subject: [PATCH 3/3] Move MultiplayerHubContext to singleton service --- .../Multiplayer/MatchTypeTests.cs | 6 +++--- .../Multiplayer/MultiplayerTest.cs | 15 +++++++++++++-- .../Multiplayer/TestMultiplayerHub.cs | 5 +---- .../Extensions/ServiceCollectionExtensions.cs | 3 ++- .../Hubs/Multiplayer/MultiplayerHub.cs | 7 +++---- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/osu.Server.Spectator.Tests/Multiplayer/MatchTypeTests.cs b/osu.Server.Spectator.Tests/Multiplayer/MatchTypeTests.cs index 5632cd97..6d7fd9d3 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/MatchTypeTests.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/MatchTypeTests.cs @@ -32,7 +32,7 @@ public async Task MatchRoomStateUpdatePropagatesToUsers() room.MatchState = mockRoomState.Object; - await Hub.HubContext.NotifyMatchRoomStateChanged(room); + await HubContext.NotifyMatchRoomStateChanged(room); Receiver.Verify(c => c.MatchRoomStateChanged(mockRoomState.Object), Times.Once); } @@ -50,7 +50,7 @@ public async Task MatchEventPropagatesToUsers() var mockEvent = new Mock(); - await Hub.HubContext.NotifyNewMatchEvent(room, mockEvent.Object); + await HubContext.NotifyNewMatchEvent(room, mockEvent.Object); Receiver.Verify(c => c.MatchEvent(mockEvent.Object), Times.Once); } @@ -72,7 +72,7 @@ public async Task MatchUserStateUpdatePropagatesToUsers() user.MatchState = mockRoomState.Object; - await Hub.HubContext.NotifyMatchUserStateChanged(room, user); + await HubContext.NotifyMatchUserStateChanged(room, user); Receiver.Verify(c => c.MatchUserStateChanged(user.UserID, mockRoomState.Object), Times.Once); } diff --git a/osu.Server.Spectator.Tests/Multiplayer/MultiplayerTest.cs b/osu.Server.Spectator.Tests/Multiplayer/MultiplayerTest.cs index e9690be2..4ed48415 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/MultiplayerTest.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/MultiplayerTest.cs @@ -32,6 +32,7 @@ public abstract class MultiplayerTest protected const long ROOM_ID = 8888; protected const long ROOM_ID_2 = 9999; + protected IMultiplayerHubContext HubContext { get; } protected TestMultiplayerHub Hub { get; } protected EntityStore Rooms { get; } protected EntityStore UserStates { get; } @@ -138,15 +139,25 @@ protected MultiplayerTest() LegacyIO.Setup(io => io.CreateRoomAsync(It.IsAny(), It.IsAny())) .Returns((_, room) => Task.FromResult(room.RoomID)); + MultiplayerEventLogger eventLogger = new MultiplayerEventLogger(loggerFactoryMock.Object, DatabaseFactory.Object); + + HubContext = new MultiplayerHubContext( + hubContext.Object, + Rooms, + UserStates, + loggerFactoryMock.Object, + DatabaseFactory.Object, + eventLogger); + Hub = new TestMultiplayerHub( loggerFactoryMock.Object, Rooms, UserStates, DatabaseFactory.Object, new ChatFilters(DatabaseFactory.Object), - hubContext.Object, + HubContext, LegacyIO.Object, - new MultiplayerEventLogger(loggerFactoryMock.Object, DatabaseFactory.Object), + eventLogger, new Mock().Object); Hub.Groups = Groups.Object; Hub.Clients = Clients.Object; diff --git a/osu.Server.Spectator.Tests/Multiplayer/TestMultiplayerHub.cs b/osu.Server.Spectator.Tests/Multiplayer/TestMultiplayerHub.cs index c85ac82b..dc1e9281 100644 --- a/osu.Server.Spectator.Tests/Multiplayer/TestMultiplayerHub.cs +++ b/osu.Server.Spectator.Tests/Multiplayer/TestMultiplayerHub.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using osu.Server.Spectator.Database; using osu.Server.Spectator.Entities; @@ -13,15 +12,13 @@ namespace osu.Server.Spectator.Tests.Multiplayer { public class TestMultiplayerHub : MultiplayerHub { - public new MultiplayerHubContext HubContext => base.HubContext; - public TestMultiplayerHub( ILoggerFactory loggerFactory, EntityStore rooms, EntityStore users, IDatabaseFactory databaseFactory, ChatFilters chatFilters, - IHubContext hubContext, + IMultiplayerHubContext hubContext, ISharedInterop sharedInterop, MultiplayerEventLogger multiplayerEventLogger, IMatchmakingQueueBackgroundService matchmakingQueueBackgroundService) diff --git a/osu.Server.Spectator/Extensions/ServiceCollectionExtensions.cs b/osu.Server.Spectator/Extensions/ServiceCollectionExtensions.cs index 04fdc8a9..be827595 100644 --- a/osu.Server.Spectator/Extensions/ServiceCollectionExtensions.cs +++ b/osu.Server.Spectator/Extensions/ServiceCollectionExtensions.cs @@ -37,7 +37,8 @@ public static IServiceCollection AddHubEntities(this IServiceCollection serviceC .AddHostedService(ctx => ctx.GetRequiredService()) .AddSingleton() .AddSingleton() - .AddHostedService(ctx => ctx.GetRequiredService()); + .AddHostedService(ctx => ctx.GetRequiredService()) + .AddSingleton(); } /// diff --git a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs index f4be3e29..71e7f93e 100644 --- a/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs +++ b/osu.Server.Spectator/Hubs/Multiplayer/MultiplayerHub.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading.Tasks; using MessagePack; -using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using osu.Game.Online; using osu.Game.Online.API; @@ -26,7 +25,7 @@ public partial class MultiplayerHub : StatefulUserHub Rooms; - protected readonly MultiplayerHubContext HubContext; + protected readonly IMultiplayerHubContext HubContext; private readonly IDatabaseFactory databaseFactory; private readonly ChatFilters chatFilters; private readonly ISharedInterop sharedInterop; @@ -39,7 +38,7 @@ public MultiplayerHub( EntityStore users, IDatabaseFactory databaseFactory, ChatFilters chatFilters, - IHubContext hubContext, + IMultiplayerHubContext hubContext, ISharedInterop sharedInterop, MultiplayerEventLogger multiplayerEventLogger, IMatchmakingQueueBackgroundService matchmakingQueueService) @@ -52,7 +51,7 @@ public MultiplayerHub( this.matchmakingQueueService = matchmakingQueueService; Rooms = rooms; - HubContext = new MultiplayerHubContext(hubContext, rooms, users, loggerFactory, databaseFactory, multiplayerEventLogger); + HubContext = hubContext; } public async Task CreateRoom(MultiplayerRoom room)