Merge branch 'master' into remove-unnecessary-queue

This commit is contained in:
Dan Balasescu 2021-08-20 11:31:32 +09:00 committed by GitHub
commit cde1b473fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 292 additions and 319 deletions

View File

@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private RoomsContainer container;
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen;

View File

@ -11,7 +11,6 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
@ -35,18 +34,6 @@ namespace osu.Game.Tests.Visual.Playlists
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
}
return false;
};
}
[SetUpSteps]

View File

@ -76,6 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] IdleTracker idleTracker)
{
const float controls_area_height = 25f;
if (idleTracker != null)
isIdle.BindTo(idleTracker.IsIdle);
@ -84,86 +86,73 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
InternalChildren = new Drawable[]
{
ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
loadingLayer = new LoadingLayer(true),
new Container
{
Name = @"Rooms area",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Left = WaveOverlayContainer.WIDTH_PADDING,
Right = WaveOverlayContainer.WIDTH_PADDING,
Horizontal = WaveOverlayContainer.WIDTH_PADDING,
Top = Header.HEIGHT + controls_area_height + 20,
},
Child = new GridContainer
Child = scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer
{
new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
new Dimension(GridSizeMode.Absolute, 25),
new Dimension(GridSizeMode.Absolute, 20)
Filter = { BindTarget = filter }
}
},
},
loadingLayer = new LoadingLayer(true),
new FillFlowContainer
{
Name = @"Header area flow",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = Header.HEIGHT,
Child = searchTextBox = new LoungeSearchTextBox
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.X,
Width = 0.6f,
},
},
Content = new[]
new Container
{
new Drawable[]
RelativeSizeAxes = Axes.X,
Height = controls_area_height,
Children = new Drawable[]
{
searchTextBox = new LoungeSearchTextBox
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.X,
Width = 0.6f,
},
},
new Drawable[]
{
new Container
d.Anchor = Anchor.BottomLeft;
d.Origin = Anchor.BottomLeft;
d.Size = new Vector2(150, 37.5f);
d.Action = () => Open();
})),
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue, // Contained filters should appear over the top of rooms.
Children = new Drawable[]
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
{
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
d.Anchor = Anchor.BottomLeft;
d.Origin = Anchor.BottomLeft;
d.Size = new Vector2(150, 37.5f);
d.Action = () => Open();
})),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
{
d.Anchor = Anchor.TopRight;
d.Origin = Anchor.TopRight;
}))
}
}
d.Anchor = Anchor.TopRight;
d.Origin = Anchor.TopRight;
}))
}
},
null,
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
scrollContainer = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer
{
Filter = { BindTarget = filter }
}
},
}
},
}
}
},

View File

@ -1,150 +1,36 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
/// <summary>
/// A <see cref="RoomManager"/> for use in multiplayer test scenes. Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
/// A <see cref="RoomManager"/> for use in multiplayer test scenes, backed by a <see cref="TestRoomRequestsHandler"/>.
/// Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
/// </summary>
/// <remarks>
/// This implementation will pretend to be a server, handling room retrieval and manipulation requests
/// and returning a roughly expected state, without the need for a server to be running.
/// </remarks>
public class TestRequestHandlingMultiplayerRoomManager : MultiplayerRoomManager
{
[Resolved]
private IAPIProvider api { get; set; }
public IReadOnlyList<Room> ServerSideRooms => handler.ServerSideRooms;
[Resolved]
private OsuGameBase game { get; set; }
public IReadOnlyList<Room> ServerSideRooms => serverSideRooms;
private readonly List<Room> serverSideRooms = new List<Room>();
private int currentRoomId;
private int currentPlaylistItemId;
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
[BackgroundDependencyLoader]
private void load()
private void load(IAPIProvider api, OsuGameBase game)
{
int currentScoreId = 0;
// Handling here is pretending to be a server, while also updating the local state to match
// how the server would eventually respond and update the RoomManager.
((DummyAPIAccess)api).HandleRequest = req =>
{
switch (req)
{
case CreateRoomRequest createRoomRequest:
var apiRoom = new Room();
apiRoom.CopyFrom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
AddServerSideRoom(apiRoom);
var responseRoom = new APICreatedRoom();
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
createRoomRequest.TriggerSuccess(responseRoom);
return true;
case JoinRoomRequest joinRoomRequest:
{
var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
if (joinRoomRequest.Password != room.Password.Value)
{
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
return true;
}
joinRoomRequest.TriggerSuccess();
return true;
}
case PartRoomRequest partRoomRequest:
partRoomRequest.TriggerSuccess();
return true;
case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>();
foreach (var r in ServerSideRooms)
roomsWithoutParticipants.Add(createResponseRoom(r, false));
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
return true;
case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true;
case GetBeatmapSetRequest getBeatmapSetRequest:
var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
// Get the online API from the game's dependencies.
game.Dependencies.Get<IAPIProvider>().Queue(onlineReq);
return true;
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
case SubmitRoomScoreRequest submitRoomScoreRequest:
submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
{
ID = currentScoreId++,
Accuracy = 1,
EndedAt = DateTimeOffset.Now,
Passed = true,
Rank = ScoreRank.S,
MaxCombo = 1000,
TotalScore = 1000000,
User = api.LocalUser.Value,
Statistics = new Dictionary<HitResult, int>()
});
return true;
}
return false;
};
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
public void AddServerSideRoom(Room room)
{
room.RoomID.Value ??= currentRoomId++;
for (int i = 0; i < room.Playlist.Count; i++)
room.Playlist[i].ID = currentPlaylistItemId++;
serverSideRooms.Add(room);
}
private Room createResponseRoom(Room room, bool withParticipants)
{
var responseRoom = new Room();
responseRoom.CopyFrom(room);
responseRoom.Password.Value = null;
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
return responseRoom;
}
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
public void AddServerSideRoom(Room room) => handler.AddServerSideRoom(room);
}
}

View File

@ -1,111 +0,0 @@
// 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.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// A very simple <see cref="RoomManager"/> for use in online play test scenes.
/// </summary>
public class BasicTestRoomManager : IRoomManager
{
public event Action RoomsUpdated;
public readonly BindableList<Room> Rooms = new BindableList<Room>();
public Action<Room, string> JoinRoomRequested;
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
IBindableList<Room> IRoomManager.Rooms => Rooms;
private int currentRoomId;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1;
onSuccess?.Invoke(room);
AddOrUpdateRoom(room);
}
public void AddOrUpdateRoom(Room room)
{
var existing = Rooms.FirstOrDefault(r => r.RoomID.Value != null && r.RoomID.Value == room.RoomID.Value);
if (existing != null)
existing.CopyFrom(room);
else
Rooms.Add(room);
RoomsUpdated?.Invoke();
}
public void RemoveRoom(Room room)
{
Rooms.Remove(room);
RoomsUpdated?.Invoke();
}
public void ClearRooms()
{
Rooms.Clear();
RoomsUpdated?.Invoke();
}
public void JoinRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
{
JoinRoomRequested?.Invoke(room, password);
onSuccess?.Invoke(room);
}
public void PartRoom()
{
}
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
{
for (int i = 0; i < count; i++)
{
var room = new Room
{
RoomID = { Value = currentRoomId },
Position = { Value = currentRoomId },
Name = { Value = $"Room {currentRoomId}" },
Host = { Value = new User { Username = "Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
Password = { Value = withPassword ? "password" : string.Empty }
};
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
{
Ruleset = { Value = ruleset },
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}
CreateRoom(room);
currentRoomId++;
}
}
}
}

View File

@ -71,6 +71,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
drawableComponents.Add(drawable);
}
protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager();
protected virtual IRoomManager CreateRoomManager() => new TestRequestHandlingRoomManager();
}
}

View File

@ -0,0 +1,75 @@
// 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 osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// A very simple <see cref="RoomManager"/> for use in online play test scenes.
/// </summary>
public class TestRequestHandlingRoomManager : RoomManager
{
public Action<Room, string> JoinRoomRequested;
private int currentRoomId;
private readonly TestRoomRequestsHandler handler = new TestRoomRequestsHandler();
[BackgroundDependencyLoader]
private void load(IAPIProvider api, OsuGameBase game)
{
((DummyAPIAccess)api).HandleRequest = request => handler.HandleRequest(request, api.LocalUser.Value, game);
}
public override void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
{
JoinRoomRequested?.Invoke(room, password);
base.JoinRoom(room, password, onSuccess, onError);
}
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
{
for (int i = 0; i < count; i++)
{
var room = new Room
{
RoomID = { Value = -currentRoomId },
Name = { Value = $@"Room {currentRoomId}" },
Host = { Value = new User { Username = @"Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
};
if (withPassword)
room.Password.Value = @"password";
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
{
Ruleset = { Value = ruleset },
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}
CreateRoom(room);
currentRoomId++;
}
}
}
}

View File

@ -0,0 +1,147 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// Represents a handler which pretends to be a server, handling room retrieval and manipulation requests
/// and returning a roughly expected state, without the need for a server to be running.
/// </summary>
public class TestRoomRequestsHandler
{
public IReadOnlyList<Room> ServerSideRooms => serverSideRooms;
private readonly List<Room> serverSideRooms = new List<Room>();
private int currentRoomId;
private int currentPlaylistItemId;
private int currentScoreId;
/// <summary>
/// Handles an API request, while also updating the local state to match
/// how the server would eventually respond and update an <see cref="RoomManager"/>.
/// </summary>
/// <param name="request">The API request to handle.</param>
/// <param name="localUser">The local user to store in responses where required.</param>
/// <param name="game">The game base for cases where actual online requests need to be sent.</param>
/// <returns>Whether the request was successfully handled.</returns>
public bool HandleRequest(APIRequest request, User localUser, OsuGameBase game)
{
switch (request)
{
case CreateRoomRequest createRoomRequest:
var apiRoom = new Room();
apiRoom.CopyFrom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
AddServerSideRoom(apiRoom);
var responseRoom = new APICreatedRoom();
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
createRoomRequest.TriggerSuccess(responseRoom);
return true;
case JoinRoomRequest joinRoomRequest:
{
var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
if (joinRoomRequest.Password != room.Password.Value)
{
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
return true;
}
joinRoomRequest.TriggerSuccess();
return true;
}
case PartRoomRequest partRoomRequest:
partRoomRequest.TriggerSuccess();
return true;
case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>();
foreach (var r in ServerSideRooms)
roomsWithoutParticipants.Add(createResponseRoom(r, false));
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
return true;
case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true;
case GetBeatmapSetRequest getBeatmapSetRequest:
var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type);
onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res);
onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e);
// Get the online API from the game's dependencies.
game.Dependencies.Get<IAPIProvider>().Queue(onlineReq);
return true;
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
case SubmitRoomScoreRequest submitRoomScoreRequest:
submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
{
ID = currentScoreId++,
Accuracy = 1,
EndedAt = DateTimeOffset.Now,
Passed = true,
Rank = ScoreRank.S,
MaxCombo = 1000,
TotalScore = 1000000,
User = localUser,
Statistics = new Dictionary<HitResult, int>()
});
return true;
}
return false;
}
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
public void AddServerSideRoom(Room room)
{
room.RoomID.Value ??= currentRoomId++;
for (int i = 0; i < room.Playlist.Count; i++)
room.Playlist[i].ID = currentPlaylistItemId++;
serverSideRooms.Add(room);
}
private Room createResponseRoom(Room room, bool withParticipants)
{
var responseRoom = new Room();
responseRoom.CopyFrom(room);
responseRoom.Password.Value = null;
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
return responseRoom;
}
}
}