2019-01-24 08:43:03 +00: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.
2018-12-11 10:07:40 +00:00
2018-12-20 11:58:34 +00:00
using System ;
2020-02-27 10:23:21 +00:00
using System.Collections.Generic ;
2020-06-26 15:19:22 +00:00
using System.Diagnostics ;
2018-12-12 10:04:11 +00:00
using System.Linq ;
2019-02-08 04:01:10 +00:00
using System.Threading.Tasks ;
2018-12-11 10:07:40 +00:00
using osu.Framework.Allocation ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2020-02-27 10:23:21 +00:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2018-12-12 10:04:11 +00:00
using osu.Framework.Logging ;
using osu.Game.Beatmaps ;
2019-02-08 04:01:10 +00:00
using osu.Game.Online ;
2018-12-11 10:07:40 +00:00
using osu.Game.Online.API ;
using osu.Game.Online.Multiplayer ;
2018-12-12 10:04:11 +00:00
using osu.Game.Rulesets ;
2019-02-08 04:01:10 +00:00
using osu.Game.Screens.Multi.Lounge.Components ;
2018-12-11 10:07:40 +00:00
namespace osu.Game.Screens.Multi
{
2020-02-27 10:23:21 +00:00
public class RoomManager : CompositeDrawable , IRoomManager
2018-12-11 10:07:40 +00:00
{
2018-12-27 16:45:19 +00:00
public event Action RoomsUpdated ;
2019-01-07 09:50:27 +00:00
private readonly BindableList < Room > rooms = new BindableList < Room > ( ) ;
2020-06-05 11:52:27 +00:00
public Bindable < bool > InitialRoomsReceived { get ; } = new Bindable < bool > ( ) ;
2019-01-07 09:50:27 +00:00
public IBindableList < Room > Rooms = > rooms ;
2018-12-11 10:07:40 +00:00
2020-02-27 10:23:21 +00:00
public double TimeBetweenListingPolls
{
get = > listingPollingComponent . TimeBetweenPolls ;
set = > listingPollingComponent . TimeBetweenPolls = value ;
}
public double TimeBetweenSelectionPolls
{
get = > selectionPollingComponent . TimeBetweenPolls ;
set = > selectionPollingComponent . TimeBetweenPolls = value ;
}
2019-02-06 04:59:11 +00:00
2019-02-08 05:57:51 +00:00
[Resolved]
2020-02-27 10:23:21 +00:00
private RulesetStore rulesets { get ; set ; }
2019-02-08 04:02:17 +00:00
2018-12-11 10:07:40 +00:00
[Resolved]
2020-02-27 10:23:21 +00:00
private BeatmapManager beatmaps { get ; set ; }
2018-12-11 10:07:40 +00:00
2018-12-12 10:04:11 +00:00
[Resolved]
2020-02-27 10:23:21 +00:00
private IAPIProvider api { get ; set ; }
2018-12-12 10:04:11 +00:00
[Resolved]
2020-02-27 10:23:21 +00:00
private Bindable < Room > selectedRoom { get ; set ; }
private readonly ListingPollingComponent listingPollingComponent ;
private readonly SelectionPollingComponent selectionPollingComponent ;
private Room joinedRoom ;
2018-12-12 10:04:11 +00:00
2020-02-27 10:23:21 +00:00
public RoomManager ( )
2019-02-08 04:01:10 +00:00
{
2020-02-27 10:23:21 +00:00
RelativeSizeAxes = Axes . Both ;
InternalChildren = new Drawable [ ]
2019-02-08 04:01:10 +00:00
{
2020-06-05 11:52:27 +00:00
listingPollingComponent = new ListingPollingComponent
{
InitialRoomsReceived = { BindTarget = InitialRoomsReceived } ,
RoomsReceived = onListingReceived
} ,
2020-02-27 11:05:12 +00:00
selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived }
2020-02-27 10:23:21 +00:00
} ;
2019-02-08 04:01:10 +00:00
}
2018-12-26 11:12:51 +00:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
PartRoom ( ) ;
}
2018-12-26 12:10:31 +00:00
public void CreateRoom ( Room room , Action < Room > onSuccess = null , Action < string > onError = null )
2018-12-11 10:07:40 +00:00
{
2019-02-21 09:56:34 +00:00
room . Host . Value = api . LocalUser . Value ;
2018-12-11 10:07:40 +00:00
2018-12-12 10:04:11 +00:00
var req = new CreateRoomRequest ( room ) ;
2018-12-26 12:32:12 +00:00
2018-12-26 11:32:01 +00:00
req . Success + = result = >
{
2019-02-08 06:20:11 +00:00
joinedRoom = room ;
update ( room , result ) ;
addRoom ( room ) ;
2018-12-27 16:45:19 +00:00
2019-02-08 06:20:11 +00:00
RoomsUpdated ? . Invoke ( ) ;
onSuccess ? . Invoke ( room ) ;
2018-12-26 11:32:01 +00:00
} ;
2018-12-26 09:38:58 +00:00
req . Failure + = exception = >
{
if ( req . Result ! = null )
onError ? . Invoke ( req . Result . Error ) ;
else
Logger . Log ( $"Failed to create the room: {exception}" , level : LogLevel . Important ) ;
} ;
2018-12-20 11:58:34 +00:00
2018-12-12 10:04:11 +00:00
api . Queue ( req ) ;
}
2018-12-20 11:58:34 +00:00
private JoinRoomRequest currentJoinRoomRequest ;
2018-12-26 12:10:31 +00:00
public void JoinRoom ( Room room , Action < Room > onSuccess = null , Action < string > onError = null )
2018-12-20 11:58:34 +00:00
{
currentJoinRoomRequest ? . Cancel ( ) ;
2020-07-14 04:07:17 +00:00
currentJoinRoomRequest = new JoinRoomRequest ( room ) ;
2019-10-30 05:41:54 +00:00
2018-12-20 11:58:34 +00:00
currentJoinRoomRequest . Success + = ( ) = >
{
2019-02-08 06:20:11 +00:00
joinedRoom = room ;
2018-12-26 12:10:31 +00:00
onSuccess ? . Invoke ( room ) ;
2018-12-20 11:58:34 +00:00
} ;
2018-12-26 12:10:31 +00:00
currentJoinRoomRequest . Failure + = exception = >
{
2019-10-30 05:38:06 +00:00
if ( ! ( exception is OperationCanceledException ) )
Logger . Log ( $"Failed to join room: {exception}" , level : LogLevel . Important ) ;
2018-12-26 12:10:31 +00:00
onError ? . Invoke ( exception . ToString ( ) ) ;
} ;
2018-12-20 11:58:34 +00:00
api . Queue ( currentJoinRoomRequest ) ;
}
public void PartRoom ( )
{
2019-10-30 05:41:54 +00:00
currentJoinRoomRequest ? . Cancel ( ) ;
2019-02-06 04:59:11 +00:00
if ( joinedRoom = = null )
2018-12-20 11:58:34 +00:00
return ;
2020-07-14 04:07:17 +00:00
api . Queue ( new PartRoomRequest ( joinedRoom ) ) ;
2019-02-06 04:59:11 +00:00
joinedRoom = null ;
2018-12-20 11:58:34 +00:00
}
2020-06-28 13:45:13 +00:00
private readonly HashSet < int > ignoredRooms = new HashSet < int > ( ) ;
2020-06-26 15:19:22 +00:00
2020-02-27 10:23:21 +00:00
/// <summary>
/// Invoked when the listing of all <see cref="Room"/>s is received from the server.
/// </summary>
/// <param name="listing">The listing.</param>
2020-02-27 11:05:12 +00:00
private void onListingReceived ( List < Room > listing )
2018-12-19 09:02:05 +00:00
{
2020-02-27 10:23:21 +00:00
// Remove past matches
foreach ( var r in rooms . ToList ( ) )
2019-02-05 06:38:19 +00:00
{
2020-02-27 10:23:21 +00:00
if ( listing . All ( e = > e . RoomID . Value ! = r . RoomID . Value ) )
rooms . Remove ( r ) ;
}
2019-02-08 04:01:10 +00:00
2020-02-27 10:23:21 +00:00
for ( int i = 0 ; i < listing . Count ; i + + )
{
if ( selectedRoom . Value ? . RoomID ? . Value = = listing [ i ] . RoomID . Value )
2019-02-08 04:01:10 +00:00
{
2020-02-27 10:23:21 +00:00
// 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 ;
2019-02-08 04:01:10 +00:00
}
2020-06-28 13:45:13 +00:00
var room = listing [ i ] ;
Debug . Assert ( room . RoomID . Value ! = null ) ;
if ( ignoredRooms . Contains ( room . RoomID . Value . Value ) )
continue ;
room . Position . Value = i ;
2018-12-11 10:07:40 +00:00
2020-06-26 15:16:16 +00:00
try
{
2020-06-28 13:45:13 +00:00
update ( room , room ) ;
addRoom ( room ) ;
2020-06-26 15:16:16 +00:00
}
catch ( Exception ex )
{
2020-06-28 13:45:13 +00:00
Logger . Error ( ex , $"Failed to update room: {room.Name.Value}." ) ;
2020-06-26 15:19:22 +00:00
2020-06-28 13:45:13 +00:00
ignoredRooms . Add ( room . RoomID . Value . Value ) ;
rooms . Remove ( room ) ;
2020-06-26 15:16:16 +00:00
}
2020-02-27 10:23:21 +00:00
}
2018-12-12 10:04:11 +00:00
2020-02-27 10:23:21 +00:00
RoomsUpdated ? . Invoke ( ) ;
}
2019-02-08 04:01:10 +00:00
2020-02-27 10:23:21 +00:00
/// <summary>
/// Invoked when a <see cref="Room"/> is received from the server.
/// </summary>
/// <param name="toUpdate">The received <see cref="Room"/>.</param>
2020-02-27 11:05:12 +00:00
private void onSelectedRoomReceived ( Room toUpdate )
2020-02-27 10:23:21 +00:00
{
foreach ( var room in rooms )
{
if ( room . RoomID . Value = = toUpdate . RoomID . Value )
{
toUpdate . Position . Value = room . Position . Value ;
update ( room , toUpdate ) ;
break ;
}
}
2018-12-12 10:04:11 +00:00
}
2018-12-26 11:32:01 +00:00
/// <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 )
2018-12-12 10:04:11 +00:00
{
2018-12-26 11:32:01 +00:00
foreach ( var pi in remote . Playlist )
pi . MapObjects ( beatmaps , rulesets ) ;
2018-12-26 13:14:49 +00:00
2018-12-27 04:30:36 +00:00
local . CopyFrom ( remote ) ;
2018-12-12 10:04:11 +00:00
}
2018-12-26 11:32:01 +00:00
/// <summary>
/// Adds a <see cref="Room"/> to the list of available rooms.
/// </summary>
2019-04-25 08:36:17 +00:00
/// <param name="room">The <see cref="Room"/> to add.</param>
2018-12-26 11:32:01 +00:00
private void addRoom ( Room room )
2018-12-17 02:04:38 +00:00
{
2018-12-26 11:32:01 +00:00
var existing = rooms . FirstOrDefault ( e = > e . RoomID . Value = = room . RoomID . Value ) ;
if ( existing = = null )
rooms . Add ( room ) ;
else
existing . CopyFrom ( room ) ;
2018-12-17 02:04:38 +00:00
}
2020-02-27 10:23:21 +00:00
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 ;
2020-06-05 11:52:27 +00:00
public readonly Bindable < bool > InitialRoomsReceived = new Bindable < bool > ( ) ;
2020-02-27 10:23:21 +00:00
[Resolved]
private IAPIProvider api { get ; set ; }
[Resolved]
private Bindable < FilterCriteria > currentFilter { get ; set ; }
[BackgroundDependencyLoader]
private void load ( )
{
currentFilter . BindValueChanged ( _ = >
{
2020-06-05 11:52:27 +00:00
InitialRoomsReceived . Value = false ;
2020-02-27 10:23:21 +00:00
if ( IsLoaded )
PollImmediately ( ) ;
} ) ;
}
private GetRoomsRequest pollReq ;
protected override Task Poll ( )
{
if ( ! api . IsLoggedIn )
return base . Poll ( ) ;
var tcs = new TaskCompletionSource < bool > ( ) ;
pollReq ? . Cancel ( ) ;
2020-07-10 08:26:42 +00:00
pollReq = new GetRoomsRequest ( currentFilter . Value . StatusFilter , currentFilter . Value . RoomCategoryFilter ) ;
2020-02-27 10:23:21 +00:00
pollReq . Success + = result = >
{
2020-06-05 11:52:27 +00:00
InitialRoomsReceived . Value = true ;
2020-02-27 10:23:21 +00:00
RoomsReceived ? . Invoke ( result ) ;
tcs . SetResult ( true ) ;
} ;
pollReq . Failure + = _ = > tcs . SetResult ( false ) ;
api . Queue ( pollReq ) ;
return tcs . Task ;
}
}
2018-12-11 10:07:40 +00:00
}
}