mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Abstract RoomManager and Multiplayer
This commit is contained in:
parent
0abe2b36b2
commit
4494bb1eb5
@ -4,6 +4,7 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Multi.Timeshift;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -17,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
public TestSceneMultiScreen()
|
||||
{
|
||||
Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer();
|
||||
var multi = new TimeshiftMultiplayer();
|
||||
|
||||
AddStep("show", () => LoadScreen(multi));
|
||||
AddUntilStep("wait for loaded", () => multi.IsLoaded);
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Screens.Multi.Timeshift;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
@ -107,14 +108,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitMultiWithEscape()
|
||||
{
|
||||
PushAndConfirm(() => new Screens.Multi.Multiplayer());
|
||||
PushAndConfirm(() => new TimeshiftMultiplayer());
|
||||
exitViaEscapeAndConfirm();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitMultiWithBackButton()
|
||||
{
|
||||
PushAndConfirm(() => new Screens.Multi.Multiplayer());
|
||||
PushAndConfirm(() => new TimeshiftMultiplayer());
|
||||
exitViaBackButtonAndConfirm();
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Multi;
|
||||
using osu.Game.Screens.Multi.Timeshift;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Screens.Menu
|
||||
@ -104,7 +104,7 @@ namespace osu.Game.Screens.Menu
|
||||
this.Push(new Editor());
|
||||
},
|
||||
OnSolo = onSolo,
|
||||
OnMulti = delegate { this.Push(new Multiplayer()); },
|
||||
OnMulti = delegate { this.Push(new TimeshiftMultiplayer()); },
|
||||
OnExit = confirmAndExit,
|
||||
}
|
||||
}
|
||||
|
68
osu.Game/Screens/Multi/Components/ListingPollingComponent.cs
Normal file
68
osu.Game/Screens/Multi/Components/ListingPollingComponent.cs
Normal file
@ -0,0 +1,68 @@
|
||||
// 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.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.Multi.Lounge.Components;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RoomPollingComponent"/> that polls for the lounge listing.
|
||||
/// </summary>
|
||||
public class ListingPollingComponent : RoomPollingComponent
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<FilterCriteria> currentFilter { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
currentFilter.BindValueChanged(_ =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
private GetRoomsRequest pollReq;
|
||||
|
||||
protected override Task Poll()
|
||||
{
|
||||
if (!API.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
pollReq?.Cancel();
|
||||
pollReq = new GetRoomsRequest(currentFilter.Value.Status, currentFilter.Value.Category);
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
{
|
||||
if (result[i].RoomID.Value == selectedRoom.Value?.RoomID.Value)
|
||||
{
|
||||
// The listing request always has less information than the opened room, so don't include it.
|
||||
result[i] = selectedRoom.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NotifyRoomsReceived(result);
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
pollReq.Failure += _ => tcs.SetResult(false);
|
||||
|
||||
API.Queue(pollReq);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
188
osu.Game/Screens/Multi/Components/RoomManager.cs
Normal file
188
osu.Game/Screens/Multi/Components/RoomManager.cs
Normal file
@ -0,0 +1,188 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
public abstract class RoomManager : CompositeDrawable, IRoomManager
|
||||
{
|
||||
public event Action RoomsUpdated;
|
||||
|
||||
private readonly BindableList<Room> rooms = new BindableList<Room>();
|
||||
|
||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>();
|
||||
|
||||
public IBindableList<Room> Rooms => rooms;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Room joinedRoom;
|
||||
|
||||
protected RoomManager()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = CreatePollingComponents().Select(p =>
|
||||
{
|
||||
p.InitialRoomsReceived.BindTo(InitialRoomsReceived);
|
||||
p.RoomsReceived = onRoomsReceived;
|
||||
return p;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
PartRoom();
|
||||
}
|
||||
|
||||
public virtual void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
room.Host.Value = api.LocalUser.Value;
|
||||
|
||||
var req = new CreateRoomRequest(room);
|
||||
|
||||
req.Success += result =>
|
||||
{
|
||||
joinedRoom = room;
|
||||
|
||||
update(room, result);
|
||||
addRoom(room);
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
onSuccess?.Invoke(room);
|
||||
};
|
||||
|
||||
req.Failure += exception =>
|
||||
{
|
||||
if (req.Result != null)
|
||||
onError?.Invoke(req.Result.Error);
|
||||
else
|
||||
Logger.Log($"Failed to create the room: {exception}", level: LogLevel.Important);
|
||||
};
|
||||
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
private JoinRoomRequest currentJoinRoomRequest;
|
||||
|
||||
public virtual void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
currentJoinRoomRequest?.Cancel();
|
||||
currentJoinRoomRequest = new JoinRoomRequest(room);
|
||||
|
||||
currentJoinRoomRequest.Success += () =>
|
||||
{
|
||||
joinedRoom = room;
|
||||
onSuccess?.Invoke(room);
|
||||
};
|
||||
|
||||
currentJoinRoomRequest.Failure += exception =>
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important);
|
||||
onError?.Invoke(exception.ToString());
|
||||
};
|
||||
|
||||
api.Queue(currentJoinRoomRequest);
|
||||
}
|
||||
|
||||
public void PartRoom()
|
||||
{
|
||||
currentJoinRoomRequest?.Cancel();
|
||||
|
||||
if (joinedRoom == null)
|
||||
return;
|
||||
|
||||
api.Queue(new PartRoomRequest(joinedRoom));
|
||||
joinedRoom = null;
|
||||
}
|
||||
|
||||
private readonly HashSet<int> ignoredRooms = new HashSet<int>();
|
||||
|
||||
private void onRoomsReceived(List<Room> received)
|
||||
{
|
||||
// Remove past matches
|
||||
foreach (var r in rooms.ToList())
|
||||
{
|
||||
if (received.All(e => e.RoomID.Value != r.RoomID.Value))
|
||||
rooms.Remove(r);
|
||||
}
|
||||
|
||||
for (int i = 0; i < received.Count; i++)
|
||||
{
|
||||
var room = received[i];
|
||||
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
if (ignoredRooms.Contains(room.RoomID.Value.Value))
|
||||
continue;
|
||||
|
||||
room.Position.Value = i;
|
||||
|
||||
try
|
||||
{
|
||||
update(room, room);
|
||||
addRoom(room);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
|
||||
|
||||
ignoredRooms.Add(room.RoomID.Value.Value);
|
||||
rooms.Remove(room);
|
||||
}
|
||||
}
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a local <see cref="Room"/> with a remote copy.
|
||||
/// </summary>
|
||||
/// <param name="local">The local <see cref="Room"/> to update.</param>
|
||||
/// <param name="remote">The remote <see cref="Room"/> to update with.</param>
|
||||
private void update(Room local, Room remote)
|
||||
{
|
||||
foreach (var pi in remote.Playlist)
|
||||
pi.MapObjects(beatmaps, rulesets);
|
||||
|
||||
local.CopyFrom(remote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Room"/> to the list of available rooms.
|
||||
/// </summary>
|
||||
/// <param name="room">The <see cref="Room"/> to add.</param>
|
||||
private void addRoom(Room room)
|
||||
{
|
||||
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
|
||||
if (existing == null)
|
||||
rooms.Add(room);
|
||||
else
|
||||
existing.CopyFrom(room);
|
||||
}
|
||||
|
||||
protected abstract RoomPollingComponent[] CreatePollingComponents();
|
||||
}
|
||||
}
|
41
osu.Game/Screens/Multi/Components/RoomPollingComponent.cs
Normal file
41
osu.Game/Screens/Multi/Components/RoomPollingComponent.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
public abstract class RoomPollingComponent : PollingComponent
|
||||
{
|
||||
public Action<List<Room>> RoomsReceived;
|
||||
|
||||
/// <summary>
|
||||
/// The time in milliseconds to wait between polls.
|
||||
/// Setting to zero stops all polling.
|
||||
/// </summary>
|
||||
public new readonly Bindable<double> TimeBetweenPolls = new Bindable<double>();
|
||||
|
||||
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
protected RoomPollingComponent()
|
||||
{
|
||||
TimeBetweenPolls.BindValueChanged(time => base.TimeBetweenPolls = time.NewValue);
|
||||
}
|
||||
|
||||
protected void NotifyRoomsReceived(List<Room> rooms)
|
||||
{
|
||||
initialRoomsReceived.Value = true;
|
||||
RoomsReceived?.Invoke(rooms);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="RoomPollingComponent"/> that polls for the currently-selected room.
|
||||
/// </summary>
|
||||
public class SelectionPollingComponent : RoomPollingComponent
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IRoomManager roomManager { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
selectedRoom.BindValueChanged(_ =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
private GetRoomRequest pollReq;
|
||||
|
||||
protected override Task Poll()
|
||||
{
|
||||
if (!API.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
if (selectedRoom.Value?.RoomID.Value == null)
|
||||
return base.Poll();
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
pollReq?.Cancel();
|
||||
pollReq = new GetRoomRequest(selectedRoom.Value.RoomID.Value.Value);
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
var rooms = new List<Room>(roomManager.Rooms);
|
||||
|
||||
int index = rooms.FindIndex(r => r.RoomID == result.RoomID);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
rooms[index] = result;
|
||||
|
||||
NotifyRoomsReceived(rooms);
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
pollReq.Failure += _ => tcs.SetResult(false);
|
||||
|
||||
API.Queue(pollReq);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.Multi
|
||||
{
|
||||
[Cached(typeof(IRoomManager))]
|
||||
public interface IRoomManager
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
@ -30,7 +29,7 @@ using osuTK;
|
||||
namespace osu.Game.Screens.Multi
|
||||
{
|
||||
[Cached]
|
||||
public class Multiplayer : OsuScreen
|
||||
public abstract class Multiplayer : OsuScreen
|
||||
{
|
||||
public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true;
|
||||
|
||||
@ -46,6 +45,9 @@ namespace osu.Game.Screens.Multi
|
||||
|
||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||
|
||||
[Cached(Type = typeof(IRoomManager))]
|
||||
protected IRoomManager RoomManager { get; private set; }
|
||||
|
||||
[Cached]
|
||||
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||
|
||||
@ -55,9 +57,6 @@ namespace osu.Game.Screens.Multi
|
||||
[Resolved(CanBeNull = true)]
|
||||
private MusicController music { get; set; }
|
||||
|
||||
[Cached(Type = typeof(IRoomManager))]
|
||||
private RoomManager roomManager;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
|
||||
@ -70,7 +69,7 @@ namespace osu.Game.Screens.Multi
|
||||
private readonly Drawable header;
|
||||
private readonly Drawable headerBackground;
|
||||
|
||||
public Multiplayer()
|
||||
protected Multiplayer()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
@ -82,7 +81,7 @@ namespace osu.Game.Screens.Multi
|
||||
InternalChild = waves = new MultiplayerWaveContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
@ -137,7 +136,7 @@ namespace osu.Game.Screens.Multi
|
||||
Origin = Anchor.TopRight,
|
||||
Action = () => CreateRoom()
|
||||
},
|
||||
roomManager = new RoomManager()
|
||||
(Drawable)(RoomManager = CreateRoomManager())
|
||||
}
|
||||
};
|
||||
|
||||
@ -168,7 +167,7 @@ namespace osu.Game.Screens.Multi
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
isIdle.BindValueChanged(idle => updatePollingRate(idle.NewValue), true);
|
||||
isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true);
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -178,36 +177,7 @@ namespace osu.Game.Screens.Multi
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void updatePollingRate(bool idle)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
roomManager.TimeBetweenListingPolls = 0;
|
||||
roomManager.TimeBetweenSelectionPolls = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (screenStack.CurrentScreen)
|
||||
{
|
||||
case LoungeSubScreen _:
|
||||
roomManager.TimeBetweenListingPolls = idle ? 120000 : 15000;
|
||||
roomManager.TimeBetweenSelectionPolls = idle ? 120000 : 15000;
|
||||
break;
|
||||
|
||||
case MatchSubScreen _:
|
||||
roomManager.TimeBetweenListingPolls = 0;
|
||||
roomManager.TimeBetweenSelectionPolls = idle ? 30000 : 5000;
|
||||
break;
|
||||
|
||||
default:
|
||||
roomManager.TimeBetweenListingPolls = 0;
|
||||
roomManager.TimeBetweenSelectionPolls = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})");
|
||||
}
|
||||
protected abstract void UpdatePollingRate(bool isIdle);
|
||||
|
||||
private void forcefullyExit()
|
||||
{
|
||||
@ -241,7 +211,7 @@ namespace osu.Game.Screens.Multi
|
||||
|
||||
beginHandlingTrack();
|
||||
|
||||
updatePollingRate(isIdle.Value);
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
@ -251,12 +221,12 @@ namespace osu.Game.Screens.Multi
|
||||
|
||||
endHandlingTrack();
|
||||
|
||||
updatePollingRate(isIdle.Value);
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
roomManager.PartRoom();
|
||||
RoomManager.PartRoom();
|
||||
|
||||
waves.Hide();
|
||||
|
||||
@ -344,12 +314,14 @@ namespace osu.Game.Screens.Multi
|
||||
if (newScreen is IOsuScreen newOsuScreen)
|
||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||
|
||||
updatePollingRate(isIdle.Value);
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
|
||||
|
||||
updateTrack();
|
||||
}
|
||||
|
||||
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||
|
||||
private void updateTrack(ValueChangedEvent<WorkingBeatmap> _ = null)
|
||||
{
|
||||
if (screenStack.CurrentScreen is MatchSubScreen)
|
||||
@ -381,6 +353,8 @@ namespace osu.Game.Screens.Multi
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract IRoomManager CreateRoomManager();
|
||||
|
||||
private class MultiplayerWaveContainer : WaveContainer
|
||||
{
|
||||
protected override bool StartHidden => true;
|
||||
|
@ -1,337 +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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Multi.Lounge.Components;
|
||||
|
||||
namespace osu.Game.Screens.Multi
|
||||
{
|
||||
public class RoomManager : CompositeDrawable, IRoomManager
|
||||
{
|
||||
public event Action RoomsUpdated;
|
||||
|
||||
private readonly BindableList<Room> rooms = new BindableList<Room>();
|
||||
|
||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>();
|
||||
|
||||
public IBindableList<Room> Rooms => rooms;
|
||||
|
||||
public double TimeBetweenListingPolls
|
||||
{
|
||||
get => listingPollingComponent.TimeBetweenPolls;
|
||||
set => listingPollingComponent.TimeBetweenPolls = value;
|
||||
}
|
||||
|
||||
public double TimeBetweenSelectionPolls
|
||||
{
|
||||
get => selectionPollingComponent.TimeBetweenPolls;
|
||||
set => selectionPollingComponent.TimeBetweenPolls = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
|
||||
private readonly ListingPollingComponent listingPollingComponent;
|
||||
private readonly SelectionPollingComponent selectionPollingComponent;
|
||||
|
||||
private Room joinedRoom;
|
||||
|
||||
public RoomManager()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
listingPollingComponent = new ListingPollingComponent
|
||||
{
|
||||
InitialRoomsReceived = { BindTarget = InitialRoomsReceived },
|
||||
RoomsReceived = onListingReceived
|
||||
},
|
||||
selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
PartRoom();
|
||||
}
|
||||
|
||||
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
room.Host.Value = api.LocalUser.Value;
|
||||
|
||||
var req = new CreateRoomRequest(room);
|
||||
|
||||
req.Success += result =>
|
||||
{
|
||||
joinedRoom = room;
|
||||
|
||||
update(room, result);
|
||||
addRoom(room);
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
onSuccess?.Invoke(room);
|
||||
};
|
||||
|
||||
req.Failure += exception =>
|
||||
{
|
||||
if (req.Result != null)
|
||||
onError?.Invoke(req.Result.Error);
|
||||
else
|
||||
Logger.Log($"Failed to create the room: {exception}", level: LogLevel.Important);
|
||||
};
|
||||
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
private JoinRoomRequest currentJoinRoomRequest;
|
||||
|
||||
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
currentJoinRoomRequest?.Cancel();
|
||||
currentJoinRoomRequest = new JoinRoomRequest(room);
|
||||
|
||||
currentJoinRoomRequest.Success += () =>
|
||||
{
|
||||
joinedRoom = room;
|
||||
onSuccess?.Invoke(room);
|
||||
};
|
||||
|
||||
currentJoinRoomRequest.Failure += exception =>
|
||||
{
|
||||
if (!(exception is OperationCanceledException))
|
||||
Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important);
|
||||
onError?.Invoke(exception.ToString());
|
||||
};
|
||||
|
||||
api.Queue(currentJoinRoomRequest);
|
||||
}
|
||||
|
||||
public void PartRoom()
|
||||
{
|
||||
currentJoinRoomRequest?.Cancel();
|
||||
|
||||
if (joinedRoom == null)
|
||||
return;
|
||||
|
||||
api.Queue(new PartRoomRequest(joinedRoom));
|
||||
joinedRoom = null;
|
||||
}
|
||||
|
||||
private readonly HashSet<int> ignoredRooms = new HashSet<int>();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the listing of all <see cref="Room"/>s is received from the server.
|
||||
/// </summary>
|
||||
/// <param name="listing">The listing.</param>
|
||||
private void onListingReceived(List<Room> listing)
|
||||
{
|
||||
// Remove past matches
|
||||
foreach (var r in rooms.ToList())
|
||||
{
|
||||
if (listing.All(e => e.RoomID.Value != r.RoomID.Value))
|
||||
rooms.Remove(r);
|
||||
}
|
||||
|
||||
for (int i = 0; i < listing.Count; i++)
|
||||
{
|
||||
if (selectedRoom.Value?.RoomID?.Value == listing[i].RoomID.Value)
|
||||
{
|
||||
// The listing request contains less data than the selection request, so data from the selection request is always preferred while the room is selected.
|
||||
continue;
|
||||
}
|
||||
|
||||
var room = listing[i];
|
||||
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
if (ignoredRooms.Contains(room.RoomID.Value.Value))
|
||||
continue;
|
||||
|
||||
room.Position.Value = i;
|
||||
|
||||
try
|
||||
{
|
||||
update(room, room);
|
||||
addRoom(room);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
|
||||
|
||||
ignoredRooms.Add(room.RoomID.Value.Value);
|
||||
rooms.Remove(room);
|
||||
}
|
||||
}
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a <see cref="Room"/> is received from the server.
|
||||
/// </summary>
|
||||
/// <param name="toUpdate">The received <see cref="Room"/>.</param>
|
||||
private void onSelectedRoomReceived(Room toUpdate)
|
||||
{
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
if (room.RoomID.Value == toUpdate.RoomID.Value)
|
||||
{
|
||||
toUpdate.Position.Value = room.Position.Value;
|
||||
update(room, toUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a local <see cref="Room"/> with a remote copy.
|
||||
/// </summary>
|
||||
/// <param name="local">The local <see cref="Room"/> to update.</param>
|
||||
/// <param name="remote">The remote <see cref="Room"/> to update with.</param>
|
||||
private void update(Room local, Room remote)
|
||||
{
|
||||
foreach (var pi in remote.Playlist)
|
||||
pi.MapObjects(beatmaps, rulesets);
|
||||
|
||||
local.CopyFrom(remote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Room"/> to the list of available rooms.
|
||||
/// </summary>
|
||||
/// <param name="room">The <see cref="Room"/> to add.</param>
|
||||
private void addRoom(Room room)
|
||||
{
|
||||
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
|
||||
if (existing == null)
|
||||
rooms.Add(room);
|
||||
else
|
||||
existing.CopyFrom(room);
|
||||
}
|
||||
|
||||
private class SelectionPollingComponent : PollingComponent
|
||||
{
|
||||
public Action<Room> RoomReceived;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
selectedRoom.BindValueChanged(_ =>
|
||||
{
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
private GetRoomRequest pollReq;
|
||||
|
||||
protected override Task Poll()
|
||||
{
|
||||
if (!api.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
if (selectedRoom.Value?.RoomID.Value == null)
|
||||
return base.Poll();
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
pollReq?.Cancel();
|
||||
pollReq = new GetRoomRequest(selectedRoom.Value.RoomID.Value.Value);
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
RoomReceived?.Invoke(result);
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
pollReq.Failure += _ => tcs.SetResult(false);
|
||||
|
||||
api.Queue(pollReq);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
private class ListingPollingComponent : PollingComponent
|
||||
{
|
||||
public Action<List<Room>> RoomsReceived;
|
||||
|
||||
public readonly Bindable<bool> InitialRoomsReceived = new Bindable<bool>();
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<FilterCriteria> currentFilter { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
currentFilter.BindValueChanged(_ =>
|
||||
{
|
||||
InitialRoomsReceived.Value = false;
|
||||
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
private GetRoomsRequest pollReq;
|
||||
|
||||
protected override Task Poll()
|
||||
{
|
||||
if (!api.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
pollReq?.Cancel();
|
||||
pollReq = new GetRoomsRequest(currentFilter.Value.Status, currentFilter.Value.Category);
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
InitialRoomsReceived.Value = true;
|
||||
RoomsReceived?.Invoke(result);
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
pollReq.Failure += _ => tcs.SetResult(false);
|
||||
|
||||
api.Queue(pollReq);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs
Normal file
48
osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Screens.Multi.Lounge;
|
||||
using osu.Game.Screens.Multi.Match;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Timeshift
|
||||
{
|
||||
public class TimeshiftMultiplayer : Multiplayer
|
||||
{
|
||||
protected override void UpdatePollingRate(bool isIdle)
|
||||
{
|
||||
var timeshiftManager = (TimeshiftRoomManager)RoomManager;
|
||||
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
timeshiftManager.TimeBetweenListingPolls.Value = 0;
|
||||
timeshiftManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (CurrentSubScreen)
|
||||
{
|
||||
case LoungeSubScreen _:
|
||||
timeshiftManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
|
||||
timeshiftManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
|
||||
break;
|
||||
|
||||
case MatchSubScreen _:
|
||||
timeshiftManager.TimeBetweenListingPolls.Value = 0;
|
||||
timeshiftManager.TimeBetweenSelectionPolls.Value = isIdle ? 30000 : 5000;
|
||||
break;
|
||||
|
||||
default:
|
||||
timeshiftManager.TimeBetweenListingPolls.Value = 0;
|
||||
timeshiftManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Polling adjusted (listing: {timeshiftManager.TimeBetweenListingPolls.Value}, selection: {timeshiftManager.TimeBetweenSelectionPolls.Value})");
|
||||
}
|
||||
|
||||
protected override IRoomManager CreateRoomManager() => new TimeshiftRoomManager();
|
||||
}
|
||||
}
|
20
osu.Game/Screens/Multi/Timeshift/TimeshiftRoomManager.cs
Normal file
20
osu.Game/Screens/Multi/Timeshift/TimeshiftRoomManager.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Game.Screens.Multi.Components;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Timeshift
|
||||
{
|
||||
public class TimeshiftRoomManager : RoomManager
|
||||
{
|
||||
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
|
||||
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
|
||||
|
||||
protected override RoomPollingComponent[] CreatePollingComponents() => new RoomPollingComponent[]
|
||||
{
|
||||
new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
|
||||
new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user