// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Online.Multiplayer; using osu.Game.Online.RealtimeMultiplayer; using osu.Game.Screens.Multi.Components; namespace osu.Game.Screens.Multi.RealtimeMultiplayer { public class RealtimeRoomManager : RoomManager { [Resolved] private StatefulMultiplayerClient multiplayerClient { get; set; } public readonly Bindable TimeBetweenListingPolls = new Bindable(); public readonly Bindable TimeBetweenSelectionPolls = new Bindable(); private readonly IBindable isConnected = new Bindable(); private readonly Bindable allowPolling = new Bindable(); private ListingPollingComponent listingPollingComponent; protected override void LoadComplete() { base.LoadComplete(); isConnected.BindTo(multiplayerClient.IsConnected); isConnected.BindValueChanged(_ => Schedule(updatePolling)); JoinedRoom.BindValueChanged(_ => updatePolling()); updatePolling(); } public override void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => base.CreateRoom(room, r => joinMultiplayerRoom(r, onSuccess), onError); public override void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => base.JoinRoom(room, r => joinMultiplayerRoom(r, onSuccess), onError); public override void PartRoom() { if (JoinedRoom == null) return; var joinedRoom = JoinedRoom.Value; base.PartRoom(); multiplayerClient.LeaveRoom().Wait(); // 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. RemoveRoom(joinedRoom); // This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling. Schedule(() => listingPollingComponent.PollImmediately()); } private void joinMultiplayerRoom(Room room, Action onSuccess = null) { Debug.Assert(room.RoomID.Value != null); var joinTask = multiplayerClient.JoinRoom(room); joinTask.ContinueWith(_ => onSuccess?.Invoke(room), TaskContinuationOptions.OnlyOnRanToCompletion); joinTask.ContinueWith(t => { PartRoom(); if (t.Exception != null) Logger.Error(t.Exception, "Failed to join multiplayer room."); }, TaskContinuationOptions.NotOnRanToCompletion); } private void updatePolling() { if (!isConnected.Value) ClearRooms(); // Don't poll when not connected or when a room has been joined. allowPolling.Value = isConnected.Value && JoinedRoom.Value == null; } protected override RoomPollingComponent[] CreatePollingComponents() => new RoomPollingComponent[] { listingPollingComponent = new RealtimeListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls }, AllowPolling = { BindTarget = allowPolling } }, new RealtimeSelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls }, AllowPolling = { BindTarget = allowPolling } } }; private class RealtimeListingPollingComponent : ListingPollingComponent { public readonly IBindable AllowPolling = new Bindable(); 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(); } private class RealtimeSelectionPollingComponent : SelectionPollingComponent { public readonly IBindable AllowPolling = new Bindable(); 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(); } } }