mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge pull request #11204 from smoogipoo/abstract-room-manager
This commit is contained in:
commit
cdde156d0f
@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
createPoller(true);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||
checkCount(1);
|
||||
checkCount(2);
|
||||
checkCount(3);
|
||||
|
||||
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
||||
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||
checkCount(4);
|
||||
checkCount(4);
|
||||
checkCount(4);
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
checkCount(5);
|
||||
checkCount(5);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||
checkCount(6);
|
||||
checkCount(7);
|
||||
}
|
||||
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
createPoller(false);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||
checkCount(0);
|
||||
skip();
|
||||
checkCount(0);
|
||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
|
||||
public class TestSlowPoller : TestPoller
|
||||
{
|
||||
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
||||
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls.Value / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
||||
|
||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
|
||||
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
||||
|
||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
remove { }
|
||||
}
|
||||
|
||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
|
||||
public IBindableList<Room> Rooms => null;
|
||||
|
||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
remove => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
|
||||
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||
|
||||
HighPollRate.BindValueChanged(enabled => TimeBetweenPolls = enabled.NewValue ? 1000 : 6000, true);
|
||||
HighPollRate.BindValueChanged(enabled => TimeBetweenPolls.Value = enabled.NewValue ? 1000 : 6000, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
@ -19,22 +20,11 @@ namespace osu.Game.Online
|
||||
|
||||
private bool pollingActive;
|
||||
|
||||
private double timeBetweenPolls;
|
||||
|
||||
/// <summary>
|
||||
/// The time in milliseconds to wait between polls.
|
||||
/// Setting to zero stops all polling.
|
||||
/// </summary>
|
||||
public double TimeBetweenPolls
|
||||
{
|
||||
get => timeBetweenPolls;
|
||||
set
|
||||
{
|
||||
timeBetweenPolls = value;
|
||||
scheduledPoll?.Cancel();
|
||||
pollIfNecessary();
|
||||
}
|
||||
}
|
||||
public readonly Bindable<double> TimeBetweenPolls = new Bindable<double>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@ -42,7 +32,13 @@ namespace osu.Game.Online
|
||||
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops all polling.</param>
|
||||
protected PollingComponent(double timeBetweenPolls = 0)
|
||||
{
|
||||
TimeBetweenPolls = timeBetweenPolls;
|
||||
TimeBetweenPolls.BindValueChanged(_ =>
|
||||
{
|
||||
scheduledPoll?.Cancel();
|
||||
pollIfNecessary();
|
||||
});
|
||||
|
||||
TimeBetweenPolls.Value = timeBetweenPolls;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -60,7 +56,7 @@ namespace osu.Game.Online
|
||||
if (pollingActive) return false;
|
||||
|
||||
// don't try polling if the time between polls hasn't been set.
|
||||
if (timeBetweenPolls == 0) return false;
|
||||
if (TimeBetweenPolls.Value == 0) return false;
|
||||
|
||||
if (!lastTimePolled.HasValue)
|
||||
{
|
||||
@ -68,7 +64,7 @@ namespace osu.Game.Online
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
|
||||
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
|
||||
{
|
||||
doPoll();
|
||||
return true;
|
||||
@ -99,7 +95,7 @@ namespace osu.Game.Online
|
||||
/// </summary>
|
||||
public void PollImmediately()
|
||||
{
|
||||
lastTimePolled = Time.Current - timeBetweenPolls;
|
||||
lastTimePolled = Time.Current - TimeBetweenPolls.Value;
|
||||
scheduleNextPoll();
|
||||
}
|
||||
|
||||
@ -121,7 +117,7 @@ namespace osu.Game.Online
|
||||
|
||||
double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
|
||||
|
||||
scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
|
||||
scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, TimeBetweenPolls.Value - lastPollDuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
69
osu.Game/Screens/Multi/Components/ListingPollingComponent.cs
Normal file
69
osu.Game/Screens/Multi/Components/ListingPollingComponent.cs
Normal file
@ -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.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(_ =>
|
||||
{
|
||||
NotifyRoomsReceived(null);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
196
osu.Game/Screens/Multi/Components/RoomManager.cs
Normal file
196
osu.Game/Screens/Multi/Components/RoomManager.cs
Normal file
@ -0,0 +1,196 @@
|
||||
// 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 IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
||||
private readonly Bindable<bool> initialRoomsReceived = 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.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)
|
||||
{
|
||||
if (received == null)
|
||||
{
|
||||
rooms.Clear();
|
||||
initialRoomsReceived.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
initialRoomsReceived.Value = true;
|
||||
}
|
||||
|
||||
/// <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 IEnumerable<RoomPollingComponent> CreatePollingComponents();
|
||||
}
|
||||
}
|
29
osu.Game/Screens/Multi/Components/RoomPollingComponent.cs
Normal file
29
osu.Game/Screens/Multi/Components/RoomPollingComponent.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// 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.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
|
||||
namespace osu.Game.Screens.Multi.Components
|
||||
{
|
||||
public abstract class RoomPollingComponent : PollingComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when any <see cref="Room"/>s have been received from the API.
|
||||
/// <para>
|
||||
/// Any <see cref="Room"/>s present locally but not returned by this event are to be removed from display.
|
||||
/// If null, the display of local rooms is reset to an initial state.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Action<List<Room>> RoomsReceived;
|
||||
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
protected void NotifyRoomsReceived(List<Room> rooms) => 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.Value == result.RoomID.Value);
|
||||
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>
|
||||
@ -17,7 +19,7 @@ namespace osu.Game.Screens.Multi
|
||||
/// <summary>
|
||||
/// Whether an initial listing of rooms has been received.
|
||||
/// </summary>
|
||||
Bindable<bool> InitialRoomsReceived { get; }
|
||||
IBindable<bool> InitialRoomsReceived { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All the active <see cref="Room"/>s.
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multi.Lounge
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||
|
||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
|
||||
private Container content;
|
||||
private LoadingLayer loadingLayer;
|
||||
|
@ -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 RoomManager 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;
|
||||
@ -137,7 +136,7 @@ namespace osu.Game.Screens.Multi
|
||||
Origin = Anchor.TopRight,
|
||||
Action = () => CreateRoom()
|
||||
},
|
||||
roomManager = new RoomManager()
|
||||
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 RoomManager 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs
Normal file
49
osu.Game/Screens/Multi/Timeshift/TimeshiftMultiplayer.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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.Components;
|
||||
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 RoomManager CreateRoomManager() => new TimeshiftRoomManager();
|
||||
}
|
||||
}
|
21
osu.Game/Screens/Multi/Timeshift/TimeshiftRoomManager.cs
Normal file
21
osu.Game/Screens/Multi/Timeshift/TimeshiftRoomManager.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 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 IEnumerable<RoomPollingComponent> CreatePollingComponents() => new RoomPollingComponent[]
|
||||
{
|
||||
new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
|
||||
new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user