2020-12-19 00:18:41 +09:00
// 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 ;
2020-12-20 23:10:45 +09:00
using System.Collections.Generic ;
2020-12-19 00:18:41 +09:00
using System.Diagnostics ;
using System.Threading.Tasks ;
using osu.Framework.Allocation ;
2020-12-19 01:01:09 +09:00
using osu.Framework.Bindables ;
2020-12-23 17:08:28 +01:00
using osu.Framework.Extensions.ExceptionExtensions ;
2020-12-19 00:18:41 +09:00
using osu.Framework.Logging ;
using osu.Game.Online.Multiplayer ;
2020-12-25 13:38:11 +09:00
using osu.Game.Online.Rooms ;
using osu.Game.Online.Rooms.RoomStatuses ;
2020-12-25 16:50:00 +01:00
using osu.Game.Screens.OnlinePlay.Components ;
2020-12-19 00:18:41 +09:00
2020-12-25 16:50:00 +01:00
namespace osu.Game.Screens.OnlinePlay.Multiplayer
2020-12-19 00:18:41 +09:00
{
2020-12-25 13:38:11 +09:00
public class MultiplayerRoomManager : RoomManager
2020-12-19 00:18:41 +09:00
{
[Resolved]
2021-05-20 15:39:45 +09:00
private MultiplayerClient multiplayerClient { get ; set ; }
2020-12-19 00:18:41 +09:00
2020-12-19 01:01:09 +09:00
public readonly Bindable < double > TimeBetweenListingPolls = new Bindable < double > ( ) ;
2021-01-12 16:03:12 +09:00
public readonly Bindable < double > TimeBetweenSelectionPolls = new Bindable < double > ( ) ;
2020-12-19 01:01:09 +09:00
private readonly IBindable < bool > isConnected = new Bindable < bool > ( ) ;
2020-12-19 01:57:30 +09:00
private readonly Bindable < bool > allowPolling = new Bindable < bool > ( ) ;
2020-12-19 01:01:09 +09:00
2020-12-19 02:02:04 +09:00
private ListingPollingComponent listingPollingComponent ;
2020-12-19 01:01:09 +09:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
isConnected . BindTo ( multiplayerClient . IsConnected ) ;
2021-01-12 19:04:16 +09:00
isConnected . BindValueChanged ( _ = > Scheduler . AddOnce ( updatePolling ) ) ;
JoinedRoom . BindValueChanged ( _ = > Scheduler . AddOnce ( updatePolling ) , true ) ;
2020-12-19 01:01:09 +09:00
}
2020-12-19 00:18:41 +09:00
public override void CreateRoom ( Room room , Action < Room > onSuccess = null , Action < string > onError = null )
2021-07-10 16:08:12 +09:00
= > base . CreateRoom ( room , r = > joinMultiplayerRoom ( r , r . Password . Value , onSuccess , onError ) , onError ) ;
2020-12-19 00:18:41 +09:00
2021-07-10 16:08:12 +09:00
public override void JoinRoom ( Room room , string password = null , Action < Room > onSuccess = null , Action < string > onError = null )
2020-12-23 05:52:10 +03:00
{
2020-12-23 16:17:55 +09:00
if ( ! multiplayerClient . IsConnected . Value )
{
onError ? . Invoke ( "Not currently connected to the multiplayer server." ) ;
return ;
}
2020-12-23 13:57:48 +09:00
// this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join.
// should probably be done at a higher level, but due to the current structure of things this is the easiest place for now.
2020-12-23 05:52:10 +03:00
if ( room . Status . Value is RoomStatusEnded )
{
onError ? . Invoke ( "Cannot join an ended room." ) ;
return ;
}
2021-07-10 16:08:12 +09:00
base . JoinRoom ( room , password , r = > joinMultiplayerRoom ( r , password , onSuccess , onError ) , onError ) ;
2020-12-23 05:52:10 +03:00
}
2020-12-19 00:18:41 +09:00
2020-12-19 02:02:04 +09:00
public override void PartRoom ( )
{
2020-12-21 15:38:20 +09:00
if ( JoinedRoom . Value = = null )
2020-12-19 02:02:04 +09:00
return ;
2020-12-20 18:05:43 +09:00
var joinedRoom = JoinedRoom . Value ;
2020-12-19 02:02:04 +09:00
base . PartRoom ( ) ;
2020-12-23 17:10:34 +09:00
2021-01-29 16:32:28 +09:00
multiplayerClient . LeaveRoom ( ) ;
2020-12-19 02:02:04 +09:00
// Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case.
// This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling.
2020-12-21 00:39:31 +09:00
Schedule ( ( ) = >
{
RemoveRoom ( joinedRoom ) ;
listingPollingComponent . PollImmediately ( ) ;
} ) ;
2020-12-19 02:02:04 +09:00
}
2021-07-10 16:08:12 +09:00
private void joinMultiplayerRoom ( Room room , string password , Action < Room > onSuccess = null , Action < string > onError = null )
2020-12-19 00:18:41 +09:00
{
Debug . Assert ( room . RoomID . Value ! = null ) ;
2021-07-10 16:08:12 +09:00
multiplayerClient . JoinRoom ( room , password ) . ContinueWith ( t = >
2020-12-19 00:18:41 +09:00
{
2020-12-23 16:56:51 +09:00
if ( t . IsCompletedSuccessfully )
Schedule ( ( ) = > onSuccess ? . Invoke ( room ) ) ;
2021-01-30 23:39:01 +01:00
else if ( t . IsFaulted )
2020-12-23 16:56:51 +09:00
{
2020-12-23 17:08:28 +01:00
const string message = "Failed to join multiplayer room." ;
2020-12-23 16:56:51 +09:00
if ( t . Exception ! = null )
2020-12-23 17:08:28 +01:00
Logger . Error ( t . Exception , message ) ;
2020-12-23 16:56:51 +09:00
PartRoom ( ) ;
2020-12-23 17:08:28 +01:00
Schedule ( ( ) = > onError ? . Invoke ( t . Exception ? . AsSingular ( ) . Message ? ? message ) ) ;
2020-12-23 16:56:51 +09:00
}
} ) ;
2020-12-19 00:18:41 +09:00
}
2020-12-19 01:57:30 +09:00
private void updatePolling ( )
{
if ( ! isConnected . Value )
ClearRooms ( ) ;
// Don't poll when not connected or when a room has been joined.
2020-12-20 18:05:43 +09:00
allowPolling . Value = isConnected . Value & & JoinedRoom . Value = = null ;
2020-12-19 01:57:30 +09:00
}
2020-12-20 23:10:45 +09:00
protected override IEnumerable < RoomPollingComponent > CreatePollingComponents ( ) = > new RoomPollingComponent [ ]
2020-12-19 00:18:41 +09:00
{
2020-12-25 13:38:11 +09:00
listingPollingComponent = new MultiplayerListingPollingComponent
2020-12-19 01:01:09 +09:00
{
TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } ,
2020-12-19 01:57:30 +09:00
AllowPolling = { BindTarget = allowPolling }
2020-12-19 01:01:09 +09:00
} ,
2021-01-12 16:03:12 +09:00
new MultiplayerSelectionPollingComponent
{
TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } ,
AllowPolling = { BindTarget = allowPolling }
}
2020-12-19 00:18:41 +09:00
} ;
2020-12-19 01:01:09 +09:00
2020-12-25 13:38:11 +09:00
private class MultiplayerListingPollingComponent : ListingPollingComponent
2020-12-19 01:01:09 +09:00
{
public readonly IBindable < bool > AllowPolling = new Bindable < bool > ( ) ;
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-12-20 18:05:43 +09:00
AllowPolling . BindValueChanged ( allowPolling = >
2020-12-19 01:01:09 +09:00
{
2020-12-20 18:05:43 +09:00
if ( ! allowPolling . NewValue )
return ;
2020-12-19 01:01:09 +09:00
if ( IsLoaded )
PollImmediately ( ) ;
} ) ;
}
protected override Task Poll ( ) = > ! AllowPolling . Value ? Task . CompletedTask : base . Poll ( ) ;
}
2021-01-12 16:03:12 +09:00
private class MultiplayerSelectionPollingComponent : SelectionPollingComponent
{
public readonly IBindable < bool > AllowPolling = new Bindable < bool > ( ) ;
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
AllowPolling . BindValueChanged ( allowPolling = >
{
if ( ! allowPolling . NewValue )
return ;
if ( IsLoaded )
PollImmediately ( ) ;
} ) ;
}
protected override Task Poll ( ) = > ! AllowPolling . Value ? Task . CompletedTask : base . Poll ( ) ;
}
2020-12-19 00:18:41 +09:00
}
}