mirror of
https://github.com/ppy/osu.git
synced 2025-03-05 16:43:04 +08:00
Merge pull request #11353 from bdach/disable-repeat-multi-actions
Disable multiplayer action buttons after clicks to prevent double operations
This commit is contained in:
commit
c8d83a9fb3
@ -0,0 +1,55 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.OnlinePlay;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneCreateMultiplayerMatchButton : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
|
||||||
|
|
||||||
|
private CreateMultiplayerMatchButton button;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("create button", () => Child = button = new CreateMultiplayerMatchButton
|
||||||
|
{
|
||||||
|
Width = 200,
|
||||||
|
Height = 100,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtonEnableStateChanges()
|
||||||
|
{
|
||||||
|
IDisposable joiningRoomOperation = null;
|
||||||
|
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
|
||||||
|
AddStep("begin joining room", () => joiningRoomOperation = ongoingOperationTracker.BeginOperation());
|
||||||
|
assertButtonEnableState(false);
|
||||||
|
|
||||||
|
AddStep("end joining room", () => joiningRoomOperation.Dispose());
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
|
||||||
|
AddStep("disconnect client", () => Client.Disconnect());
|
||||||
|
assertButtonEnableState(false);
|
||||||
|
|
||||||
|
AddStep("re-connect client", () => Client.Connect());
|
||||||
|
assertButtonEnableState(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertButtonEnableState(bool enabled)
|
||||||
|
=> AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => button.Enabled.Value == enabled);
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.OnlinePlay;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -18,6 +20,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private MultiplayerMatchSubScreen screen;
|
private MultiplayerMatchSubScreen screen;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
|
||||||
|
|
||||||
public TestSceneMultiplayerMatchSubScreen()
|
public TestSceneMultiplayerMatchSubScreen()
|
||||||
: base(false)
|
: base(false)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -30,6 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private BeatmapManager beatmaps;
|
private BeatmapManager beatmaps;
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
|
private IDisposable readyClickOperation;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(GameHost host, AudioManager audio)
|
private void load(GameHost host, AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -56,6 +59,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
||||||
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
|
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
OnReadyClick = async () =>
|
||||||
|
{
|
||||||
|
readyClickOperation = OngoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
{
|
||||||
|
await Client.StartMatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Client.ToggleReady();
|
||||||
|
readyClickOperation.Dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -108,8 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
addClickButtonStep();
|
verifyGameplayStartFlow();
|
||||||
AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -124,8 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
|
AddStep("make user host", () => Client.TransferHost(Client.Room?.Users[0].UserID ?? 0));
|
||||||
|
|
||||||
addClickButtonStep();
|
verifyGameplayStartFlow();
|
||||||
AddAssert("match started", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -179,5 +193,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.MoveMouseTo(button);
|
InputManager.MoveMouseTo(button);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void verifyGameplayStartFlow()
|
||||||
|
{
|
||||||
|
addClickButtonStep();
|
||||||
|
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||||
|
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||||
|
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,23 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="MultiplayerRoomUser"/> corresponding to the local player, if available.
|
||||||
|
/// </summary>
|
||||||
|
public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the <see cref="LocalUser"/> is the host in <see cref="Room"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHost
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var localUser = LocalUser;
|
||||||
|
return localUser != null && Room?.Host != null && localUser.Equals(Room.Host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache userLookupCache { get; set; } = null!;
|
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||||
|
|
||||||
@ -178,6 +195,32 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the <see cref="LocalUser"/>'s ready state.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">If a toggle of ready state is not valid at this time.</exception>
|
||||||
|
public async Task ToggleReady()
|
||||||
|
{
|
||||||
|
var localUser = LocalUser;
|
||||||
|
|
||||||
|
if (localUser == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (localUser.State)
|
||||||
|
{
|
||||||
|
case MultiplayerUserState.Idle:
|
||||||
|
await ChangeState(MultiplayerUserState.Ready);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Ready:
|
||||||
|
await ChangeState(MultiplayerUserState.Idle);
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"Cannot toggle ready when in {localUser.State}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Task TransferHost(int userId);
|
public abstract Task TransferHost(int userId);
|
||||||
|
|
||||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -26,6 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||||
|
|
||||||
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||||
|
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||||
|
|
||||||
private FilterControl filter;
|
private FilterControl filter;
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -37,7 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
private bool joiningRoom;
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable joiningRoomOperation { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
@ -98,7 +106,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
||||||
initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true);
|
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
|
||||||
|
|
||||||
|
if (ongoingOperationTracker != null)
|
||||||
|
{
|
||||||
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
|
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -156,26 +170,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
|||||||
|
|
||||||
private void joinRequested(Room room)
|
private void joinRequested(Room room)
|
||||||
{
|
{
|
||||||
joiningRoom = true;
|
Debug.Assert(joiningRoomOperation == null);
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
|
||||||
|
|
||||||
RoomManager?.JoinRoom(room, r =>
|
RoomManager?.JoinRoom(room, r =>
|
||||||
{
|
{
|
||||||
Open(room);
|
Open(room);
|
||||||
joiningRoom = false;
|
joiningRoomOperation?.Dispose();
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = null;
|
||||||
}, _ =>
|
}, _ =>
|
||||||
{
|
{
|
||||||
joiningRoom = false;
|
joiningRoomOperation?.Dispose();
|
||||||
updateLoadingLayer();
|
joiningRoomOperation = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onInitialRoomsReceivedChanged(ValueChangedEvent<bool> received) => updateLoadingLayer();
|
|
||||||
|
|
||||||
private void updateLoadingLayer()
|
private void updateLoadingLayer()
|
||||||
{
|
{
|
||||||
if (joiningRoom || !initialRoomsReceived.Value)
|
if (operationInProgress.Value || !initialRoomsReceived.Value)
|
||||||
loadingLayer.Show();
|
loadingLayer.Show();
|
||||||
else
|
else
|
||||||
loadingLayer.Hide();
|
loadingLayer.Hide();
|
||||||
|
@ -10,14 +10,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
public class CreateMultiplayerMatchButton : PurpleTriangleButton
|
public class CreateMultiplayerMatchButton : PurpleTriangleButton
|
||||||
{
|
{
|
||||||
|
private IBindable<bool> isConnected;
|
||||||
|
private IBindable<bool> operationInProgress;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private StatefulMultiplayerClient multiplayerClient { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(StatefulMultiplayerClient multiplayerClient)
|
private void load()
|
||||||
{
|
{
|
||||||
Triangles.TriangleScale = 1.5f;
|
Triangles.TriangleScale = 1.5f;
|
||||||
|
|
||||||
Text = "Create room";
|
Text = "Create room";
|
||||||
|
|
||||||
((IBindable<bool>)Enabled).BindTo(multiplayerClient.IsConnected);
|
isConnected = multiplayerClient.IsConnected.GetBoundCopy();
|
||||||
|
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
isConnected.BindValueChanged(_ => updateState());
|
||||||
|
operationInProgress.BindValueChanged(_ => updateState(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState() => Enabled.Value = isConnected.Value && !operationInProgress.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -19,7 +20,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||||
|
|
||||||
|
public Action OnReadyClick
|
||||||
|
{
|
||||||
|
set => readyButton.OnReadyClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Drawable background;
|
private readonly Drawable background;
|
||||||
|
private readonly MultiplayerReadyButton readyButton;
|
||||||
|
|
||||||
public MultiplayerMatchFooter()
|
public MultiplayerMatchFooter()
|
||||||
{
|
{
|
||||||
@ -29,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
new MultiplayerReadyButton
|
readyButton = new MultiplayerReadyButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// 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 System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -68,6 +70,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
private readonly IBindable<bool> operationInProgress = new BindableBool();
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable applyingSettingsOperation;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -265,13 +275,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||||
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
||||||
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||||
|
|
||||||
|
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||||
|
operationInProgress.BindValueChanged(v =>
|
||||||
|
{
|
||||||
|
if (v.NewValue)
|
||||||
|
loadingLayer.Show();
|
||||||
|
else
|
||||||
|
loadingLayer.Hide();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0;
|
ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void apply()
|
private void apply()
|
||||||
@ -280,7 +299,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
hideError();
|
hideError();
|
||||||
loadingLayer.Show();
|
|
||||||
|
Debug.Assert(applyingSettingsOperation == null);
|
||||||
|
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
// If the client is already in a room, update via the client.
|
// If the client is already in a room, update via the client.
|
||||||
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
||||||
@ -313,16 +334,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void onSuccess(Room room)
|
private void onSuccess(Room room)
|
||||||
{
|
{
|
||||||
loadingLayer.Hide();
|
Debug.Assert(applyingSettingsOperation != null);
|
||||||
|
|
||||||
SettingsApplied?.Invoke();
|
SettingsApplied?.Invoke();
|
||||||
|
|
||||||
|
applyingSettingsOperation.Dispose();
|
||||||
|
applyingSettingsOperation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError(string text)
|
private void onError(string text)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(applyingSettingsOperation != null);
|
||||||
|
|
||||||
ErrorText.Text = text;
|
ErrorText.Text = text;
|
||||||
ErrorText.FadeIn(50);
|
ErrorText.FadeIn(50);
|
||||||
|
|
||||||
loadingLayer.Hide();
|
applyingSettingsOperation.Dispose();
|
||||||
|
applyingSettingsOperation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -24,15 +23,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
public Bindable<PlaylistItem> SelectedItem => button.SelectedItem;
|
public Bindable<PlaylistItem> SelectedItem => button.SelectedItem;
|
||||||
|
|
||||||
|
public Action OnReadyClick
|
||||||
|
{
|
||||||
|
set => button.Action = value;
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[CanBeNull]
|
|
||||||
private MultiplayerRoomUser localUser;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
|
private IBindable<bool> operationInProgress;
|
||||||
|
|
||||||
private SampleChannel sampleReadyCount;
|
private SampleChannel sampleReadyCount;
|
||||||
|
|
||||||
private readonly ButtonWithTrianglesExposed button;
|
private readonly ButtonWithTrianglesExposed button;
|
||||||
@ -46,7 +52,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = Vector2.One,
|
Size = Vector2.One,
|
||||||
Enabled = { Value = true },
|
Enabled = { Value = true },
|
||||||
Action = onClick
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,21 +59,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
|
sampleReadyCount = audio.Samples.Get(@"SongSelect/select-difficulty");
|
||||||
|
|
||||||
|
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
|
||||||
|
operationInProgress.BindValueChanged(_ => updateState());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRoomUpdated()
|
protected override void OnRoomUpdated()
|
||||||
{
|
{
|
||||||
base.OnRoomUpdated();
|
base.OnRoomUpdated();
|
||||||
|
|
||||||
// this method is called on leaving the room, so the local user may not exist in the room any more.
|
|
||||||
localUser = Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
|
|
||||||
|
|
||||||
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open;
|
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
|
var localUser = Client.LocalUser;
|
||||||
|
|
||||||
if (localUser == null)
|
if (localUser == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -100,6 +106,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||||
|
|
||||||
if (newCountReady != countReady)
|
if (newCountReady != countReady)
|
||||||
{
|
{
|
||||||
countReady = newCountReady;
|
countReady = newCountReady;
|
||||||
@ -132,22 +140,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClick()
|
|
||||||
{
|
|
||||||
if (localUser == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (localUser.State == MultiplayerUserState.Idle)
|
|
||||||
Client.ChangeState(MultiplayerUserState.Ready).CatchUnobservedExceptions(true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Room?.Host?.Equals(localUser) == true)
|
|
||||||
Client.StartMatch().CatchUnobservedExceptions(true);
|
|
||||||
else
|
|
||||||
Client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ButtonWithTrianglesExposed : ReadyButton
|
private class ButtonWithTrianglesExposed : ReadyButton
|
||||||
{
|
{
|
||||||
public new Triangles Triangles => base.Triangles;
|
public new Triangles Triangles => base.Triangles;
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
@ -31,10 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private StatefulMultiplayerClient client { get; set; }
|
private StatefulMultiplayerClient client { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||||
|
|
||||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||||
|
|
||||||
private IBindable<bool> isConnected;
|
private IBindable<bool> isConnected;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private IDisposable readyClickOperation;
|
||||||
|
|
||||||
public MultiplayerMatchSubScreen(Room room)
|
public MultiplayerMatchSubScreen(Room room)
|
||||||
{
|
{
|
||||||
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
|
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
|
||||||
@ -150,7 +159,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
},
|
},
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new MultiplayerMatchFooter { SelectedItem = { BindTarget = SelectedItem } }
|
new MultiplayerMatchFooter
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = SelectedItem },
|
||||||
|
OnReadyClick = onReadyClick
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
@ -196,6 +209,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
|
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault();
|
||||||
|
|
||||||
|
private void onReadyClick()
|
||||||
|
{
|
||||||
|
Debug.Assert(readyClickOperation == null);
|
||||||
|
readyClickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
if (client.IsHost && client.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
{
|
||||||
|
client.StartMatch()
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
// accessing Exception here silences any potential errors from the antecedent task
|
||||||
|
if (t.Exception != null)
|
||||||
|
{
|
||||||
|
t.CatchUnobservedExceptions(true); // will run immediately.
|
||||||
|
// gameplay was not started due to an exception; unblock button.
|
||||||
|
endOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// gameplay is starting, the button will be unblocked on load requested.
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.ToggleReady()
|
||||||
|
.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
t.CatchUnobservedExceptions(true); // will run immediately.
|
||||||
|
endOperation();
|
||||||
|
});
|
||||||
|
|
||||||
|
void endOperation()
|
||||||
|
{
|
||||||
|
Debug.Assert(readyClickOperation != null);
|
||||||
|
readyClickOperation.Dispose();
|
||||||
|
readyClickOperation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onLoadRequested()
|
private void onLoadRequested()
|
||||||
{
|
{
|
||||||
Debug.Assert(client.Room != null);
|
Debug.Assert(client.Room != null);
|
||||||
@ -203,6 +254,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
||||||
|
|
||||||
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
||||||
|
|
||||||
|
Debug.Assert(readyClickOperation != null);
|
||||||
|
|
||||||
|
readyClickOperation.Dispose();
|
||||||
|
readyClickOperation = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
52
osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
Normal file
52
osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class to track ongoing online operations' progress.
|
||||||
|
/// Can be used to disable interactivity while waiting for a response from online sources.
|
||||||
|
/// </summary>
|
||||||
|
public class OngoingOperationTracker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether there is an online operation in progress.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> InProgress => inProgress;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> inProgress = new BindableBool();
|
||||||
|
|
||||||
|
private LeasedBindable<bool> leasedInProgress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins tracking a new online operation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// An <see cref="IDisposable"/> that will automatically mark the operation as ended on disposal.
|
||||||
|
/// </returns>
|
||||||
|
/// <exception cref="InvalidOperationException">An operation has already been started.</exception>
|
||||||
|
public IDisposable BeginOperation()
|
||||||
|
{
|
||||||
|
if (leasedInProgress != null)
|
||||||
|
throw new InvalidOperationException("Cannot begin operation while another is in progress.");
|
||||||
|
|
||||||
|
leasedInProgress = inProgress.BeginLease(true);
|
||||||
|
leasedInProgress.Value = true;
|
||||||
|
|
||||||
|
return new InvokeOnDisposal(endOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endOperation()
|
||||||
|
{
|
||||||
|
if (leasedInProgress == null)
|
||||||
|
throw new InvalidOperationException("Cannot end operation multiple times.");
|
||||||
|
|
||||||
|
leasedInProgress.Return();
|
||||||
|
leasedInProgress = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,6 +53,9 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<FilterCriteria> currentFilter = new Bindable<FilterCriteria>(new FilterCriteria());
|
private readonly Bindable<FilterCriteria> currentFilter = new Bindable<FilterCriteria>(new FilterCriteria());
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private MusicController music { get; set; }
|
private MusicController music { get; set; }
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Cached]
|
[Cached]
|
||||||
public Bindable<FilterCriteria> Filter { get; }
|
public Bindable<FilterCriteria> Filter { get; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
public OngoingOperationTracker OngoingOperationTracker { get; } = new OngoingOperationTracker();
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private readonly TestMultiplayerRoomContainer content;
|
private readonly TestMultiplayerRoomContainer content;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user