mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 22:34:09 +08:00
Abstract RoomManager and Multiplayer
This commit is contained in:
parent
0abe2b36b2
commit
4494bb1eb5
@ -4,6 +4,7 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Multi.Timeshift;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
public TestSceneMultiScreen()
|
public TestSceneMultiScreen()
|
||||||
{
|
{
|
||||||
Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer();
|
var multi = new TimeshiftMultiplayer();
|
||||||
|
|
||||||
AddStep("show", () => LoadScreen(multi));
|
AddStep("show", () => LoadScreen(multi));
|
||||||
AddUntilStep("wait for loaded", () => multi.IsLoaded);
|
AddUntilStep("wait for loaded", () => multi.IsLoaded);
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Overlays.Toolbar;
|
using osu.Game.Overlays.Toolbar;
|
||||||
|
using osu.Game.Screens.Multi.Timeshift;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
using osu.Game.Screens.Select.Options;
|
using osu.Game.Screens.Select.Options;
|
||||||
@ -107,14 +108,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitMultiWithEscape()
|
public void TestExitMultiWithEscape()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new Screens.Multi.Multiplayer());
|
PushAndConfirm(() => new TimeshiftMultiplayer());
|
||||||
exitViaEscapeAndConfirm();
|
exitViaEscapeAndConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExitMultiWithBackButton()
|
public void TestExitMultiWithBackButton()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new Screens.Multi.Multiplayer());
|
PushAndConfirm(() => new TimeshiftMultiplayer());
|
||||||
exitViaBackButtonAndConfirm();
|
exitViaBackButtonAndConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Multi;
|
using osu.Game.Screens.Multi.Timeshift;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Menu
|
namespace osu.Game.Screens.Menu
|
||||||
@ -104,7 +104,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
this.Push(new Editor());
|
this.Push(new Editor());
|
||||||
},
|
},
|
||||||
OnSolo = onSolo,
|
OnSolo = onSolo,
|
||||||
OnMulti = delegate { this.Push(new Multiplayer()); },
|
OnMulti = delegate { this.Push(new TimeshiftMultiplayer()); },
|
||||||
OnExit = confirmAndExit,
|
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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Multi
|
namespace osu.Game.Screens.Multi
|
||||||
{
|
{
|
||||||
|
[Cached(typeof(IRoomManager))]
|
||||||
public interface IRoomManager
|
public interface IRoomManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
@ -30,7 +29,7 @@ using osuTK;
|
|||||||
namespace osu.Game.Screens.Multi
|
namespace osu.Game.Screens.Multi
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
public class Multiplayer : OsuScreen
|
public abstract class Multiplayer : OsuScreen
|
||||||
{
|
{
|
||||||
public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true;
|
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();
|
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||||
|
|
||||||
|
[Cached(Type = typeof(IRoomManager))]
|
||||||
|
protected IRoomManager RoomManager { get; private set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||||
|
|
||||||
@ -55,9 +57,6 @@ namespace osu.Game.Screens.Multi
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
[Cached(Type = typeof(IRoomManager))]
|
|
||||||
private RoomManager roomManager;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
private readonly Drawable header;
|
private readonly Drawable header;
|
||||||
private readonly Drawable headerBackground;
|
private readonly Drawable headerBackground;
|
||||||
|
|
||||||
public Multiplayer()
|
protected Multiplayer()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -82,7 +81,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
InternalChild = waves = new MultiplayerWaveContainer
|
InternalChild = waves = new MultiplayerWaveContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -137,7 +136,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Action = () => CreateRoom()
|
Action = () => CreateRoom()
|
||||||
},
|
},
|
||||||
roomManager = new RoomManager()
|
(Drawable)(RoomManager = CreateRoomManager())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,7 +167,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
isIdle.BindValueChanged(idle => updatePollingRate(idle.NewValue), true);
|
isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -178,36 +177,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePollingRate(bool idle)
|
protected abstract void UpdatePollingRate(bool isIdle);
|
||||||
{
|
|
||||||
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})");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forcefullyExit()
|
private void forcefullyExit()
|
||||||
{
|
{
|
||||||
@ -241,7 +211,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
|
|
||||||
beginHandlingTrack();
|
beginHandlingTrack();
|
||||||
|
|
||||||
updatePollingRate(isIdle.Value);
|
UpdatePollingRate(isIdle.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(IScreen next)
|
public override void OnSuspending(IScreen next)
|
||||||
@ -251,12 +221,12 @@ namespace osu.Game.Screens.Multi
|
|||||||
|
|
||||||
endHandlingTrack();
|
endHandlingTrack();
|
||||||
|
|
||||||
updatePollingRate(isIdle.Value);
|
UpdatePollingRate(isIdle.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
roomManager.PartRoom();
|
RoomManager.PartRoom();
|
||||||
|
|
||||||
waves.Hide();
|
waves.Hide();
|
||||||
|
|
||||||
@ -344,12 +314,14 @@ namespace osu.Game.Screens.Multi
|
|||||||
if (newScreen is IOsuScreen newOsuScreen)
|
if (newScreen is IOsuScreen newOsuScreen)
|
||||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||||
|
|
||||||
updatePollingRate(isIdle.Value);
|
UpdatePollingRate(isIdle.Value);
|
||||||
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
|
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
|
||||||
|
|
||||||
updateTrack();
|
updateTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||||
|
|
||||||
private void updateTrack(ValueChangedEvent<WorkingBeatmap> _ = null)
|
private void updateTrack(ValueChangedEvent<WorkingBeatmap> _ = null)
|
||||||
{
|
{
|
||||||
if (screenStack.CurrentScreen is MatchSubScreen)
|
if (screenStack.CurrentScreen is MatchSubScreen)
|
||||||
@ -381,6 +353,8 @@ namespace osu.Game.Screens.Multi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract IRoomManager CreateRoomManager();
|
||||||
|
|
||||||
private class MultiplayerWaveContainer : WaveContainer
|
private class MultiplayerWaveContainer : WaveContainer
|
||||||
{
|
{
|
||||||
protected override bool StartHidden => true;
|
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