1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-19 13:02:54 +08:00

Merge branch 'master' into fix-changelog-regression

This commit is contained in:
Dean Herbert 2020-12-30 01:34:18 +09:00 committed by GitHub
commit 6596e3c5e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 133 additions and 78 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1228.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.1229.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime; private double lastTrailTime;
private IBindable<float> cursorSize; private IBindable<float> cursorSize;
public LegacyCursorTrail()
{
Blending = BlendingParameters.Additive;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin, OsuConfigManager config) private void load(ISkinSource skin, OsuConfigManager config)
{ {
Texture = skin.GetTexture("cursortrail"); Texture = skin.GetTexture("cursortrail");
disjointTrail = skin.GetTexture("cursormiddle") == null; disjointTrail = skin.GetTexture("cursormiddle") == null;
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
if (Texture != null) if (Texture != null)
{ {
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.

View File

@ -0,0 +1,57 @@
// 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.Linq;
using Humanizer;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.NonVisual.Multiplayer
{
[HeadlessTest]
public class StatefulMultiplayerClientTest : MultiplayerTestScene
{
[Test]
public void TestPlayingUserTracking()
{
int id = 2000;
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
checkPlayingUserCount(0);
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
changeState(3, MultiplayerUserState.Playing);
checkPlayingUserCount(3);
changeState(3, MultiplayerUserState.Results);
checkPlayingUserCount(0);
changeState(6, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(6);
AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User));
checkPlayingUserCount(5);
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
}
private void checkPlayingUserCount(int expectedCount)
=> AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount);
private void changeState(int userCount, MultiplayerUserState state)
=> AddStep($"{"user".ToQuantity(userCount)} in {state}", () =>
{
for (int i = 0; i < userCount; ++i)
{
var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
Client.ChangeUserState(userId, state);
}
});
}
}

View File

@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
Client.PlayingUsers.Clear(); Client.CurrentMatchPlayingUserIds.Clear();
Client.PlayingUsers.AddRange(streamingClient.PlayingUsers); Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers);
Children = new Drawable[] Children = new Drawable[]
{ {
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test] [Test]
public void TestUserQuit() public void TestUserQuit()
{ {
AddRepeatStep("mark user quit", () => Client.PlayingUsers.RemoveAt(0), users); AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
} }
public class TestMultiplayerStreaming : SpectatorStreamingClient public class TestMultiplayerStreaming : SpectatorStreamingClient

View File

@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
RoomManager = RoomManager =
{ {
TimeBetweenListingPolls = { Value = 1 }, TimeBetweenListingPolls = { Value = 1 },
TimeBetweenSelectionPolls = { Value = 1 }
} }
}; };

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -293,8 +294,21 @@ namespace osu.Game.Online.API
failureCount = 0; failureCount = 0;
return true; return true;
} }
catch (HttpRequestException re)
{
log.Add($"{nameof(HttpRequestException)} while performing request {req}: {re.Message}");
handleFailure();
return false;
}
catch (SocketException se)
{
log.Add($"{nameof(SocketException)} while performing request {req}: {se.Message}");
handleFailure();
return false;
}
catch (WebException we) catch (WebException we)
{ {
log.Add($"{nameof(WebException)} while performing request {req}: {we.Message}");
handleWebException(we); handleWebException(we);
return false; return false;
} }
@ -312,7 +326,7 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
public IBindable<APIState> State => state; public IBindable<APIState> State => state;
private bool handleWebException(WebException we) private void handleWebException(WebException we)
{ {
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
@ -330,26 +344,24 @@ namespace osu.Game.Online.API
{ {
case HttpStatusCode.Unauthorized: case HttpStatusCode.Unauthorized:
Logout(); Logout();
return true; break;
case HttpStatusCode.RequestTimeout: case HttpStatusCode.RequestTimeout:
handleFailure();
break;
}
}
private void handleFailure()
{
failureCount++; failureCount++;
log.Add($@"API failure count is now {failureCount}"); log.Add($@"API failure count is now {failureCount}");
if (failureCount < 3) if (failureCount >= 3 && State.Value == APIState.Online)
// we might try again at an api level.
return false;
if (State.Value == APIState.Online)
{ {
state.Value = APIState.Failing; state.Value = APIState.Failing;
flushQueue(); flushQueue();
} }
return true;
}
return true;
} }
public bool IsLoggedIn => localUser.Value.Id > 1; public bool IsLoggedIn => localUser.Value.Id > 1;

View File

@ -42,8 +42,6 @@ namespace osu.Game.Online.Multiplayer
/// </summary> /// </summary>
public MultiplayerRoomUser? Host { get; set; } public MultiplayerRoomUser? Host { get; set; }
private object writeLock = new object();
[JsonConstructor] [JsonConstructor]
public MultiplayerRoom(in long roomId) public MultiplayerRoom(in long roomId)
{ {

View File

@ -61,9 +61,9 @@ namespace osu.Game.Online.Multiplayer
public MultiplayerRoom? Room { get; private set; } public MultiplayerRoom? Room { get; private set; }
/// <summary> /// <summary>
/// The users currently in gameplay. /// The users in the joined <see cref="Room"/> which are participating in the current gameplay loop.
/// </summary> /// </summary>
public readonly BindableList<int> PlayingUsers = new BindableList<int>(); public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
[Resolved] [Resolved]
private UserLookupCache userLookupCache { get; set; } = null!; private UserLookupCache userLookupCache { get; set; } = null!;
@ -84,7 +84,7 @@ namespace osu.Game.Online.Multiplayer
IsConnected.BindValueChanged(connected => IsConnected.BindValueChanged(connected =>
{ {
// clean up local room state on server disconnect. // clean up local room state on server disconnect.
if (!connected.NewValue) if (!connected.NewValue && Room != null)
{ {
Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important);
LeaveRoom().CatchUnobservedExceptions(); LeaveRoom().CatchUnobservedExceptions();
@ -133,6 +133,7 @@ namespace osu.Game.Online.Multiplayer
apiRoom = null; apiRoom = null;
Room = null; Room = null;
CurrentMatchPlayingUserIds.Clear();
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}, false); }, false);
@ -253,7 +254,7 @@ namespace osu.Game.Online.Multiplayer
return; return;
Room.Users.Remove(user); Room.Users.Remove(user);
PlayingUsers.Remove(user.UserID); CurrentMatchPlayingUserIds.Remove(user.UserID);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}, false); }, false);
@ -302,8 +303,7 @@ namespace osu.Game.Online.Multiplayer
Room.Users.Single(u => u.UserID == userId).State = state; Room.Users.Single(u => u.UserID == userId).State = state;
if (state != MultiplayerUserState.Playing) updateUserPlayingState(userId, state);
PlayingUsers.Remove(userId);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
}, false); }, false);
@ -337,8 +337,6 @@ namespace osu.Game.Online.Multiplayer
if (Room == null) if (Room == null)
return; return;
PlayingUsers.AddRange(Room.Users.Where(u => u.State == MultiplayerUserState.Playing).Select(u => u.UserID));
MatchStarted?.Invoke(); MatchStarted?.Invoke();
}, false); }, false);
@ -454,5 +452,24 @@ namespace osu.Game.Online.Multiplayer
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
apiRoom.Playlist.Add(playlistItem); apiRoom.Playlist.Add(playlistItem);
} }
/// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
/// </summary>
/// <param name="userId">The user's ID.</param>
/// <param name="state">The new state of the user.</param>
private void updateUserPlayingState(int userId, MultiplayerUserState state)
{
bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId);
bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay;
if (isPlaying == wasPlaying)
return;
if (isPlaying)
CurrentMatchPlayingUserIds.Add(userId);
else
CurrentMatchPlayingUserIds.Remove(userId);
}
} }
} }

View File

@ -156,11 +156,11 @@ namespace osu.Game.Screens.Menu
private void onMultiplayer() private void onMultiplayer()
{ {
if (!api.IsLoggedIn) if (api.State.Value != APIState.Online)
{ {
notifications?.Post(new SimpleNotification notifications?.Post(new SimpleNotification
{ {
Text = "You gotta be logged in to multi 'yo!", Text = "You gotta be online to multi 'yo!",
Icon = FontAwesome.Solid.Globe, Icon = FontAwesome.Solid.Globe,
Activated = () => Activated = () =>
{ {
@ -177,11 +177,11 @@ namespace osu.Game.Screens.Menu
private void onPlaylists() private void onPlaylists()
{ {
if (!api.IsLoggedIn) if (api.State.Value != APIState.Online)
{ {
notifications?.Post(new SimpleNotification notifications?.Post(new SimpleNotification
{ {
Text = "You gotta be logged in to multi 'yo!", Text = "You gotta be online to view playlists 'yo!",
Icon = FontAwesome.Solid.Globe, Icon = FontAwesome.Solid.Globe,
Activated = () => Activated = () =>
{ {

View File

@ -33,7 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!this.IsCurrentScreen()) if (!this.IsCurrentScreen())
{ {
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0; multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
} }
else else
{ {
@ -41,18 +40,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
case LoungeSubScreen _: case LoungeSubScreen _:
multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000; multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
break; break;
// Don't poll inside the match or anywhere else. // Don't poll inside the match or anywhere else.
default: default:
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0; multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
break; break;
} }
} }
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})"); Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value})");
} }
protected override Room CreateNewRoom() protected override Room CreateNewRoom()

View File

@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
Debug.Assert(client.Room != null); Debug.Assert(client.Room != null);
int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
} }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private StatefulMultiplayerClient multiplayerClient { get; set; } private StatefulMultiplayerClient multiplayerClient { get; set; }
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>(); public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
private readonly IBindable<bool> isConnected = new Bindable<bool>(); private readonly IBindable<bool> isConnected = new Bindable<bool>();
private readonly Bindable<bool> allowPolling = new Bindable<bool>(); private readonly Bindable<bool> allowPolling = new Bindable<bool>();
@ -119,11 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls }, TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls },
AllowPolling = { BindTarget = allowPolling } AllowPolling = { BindTarget = allowPolling }
}, },
new MultiplayerSelectionPollingComponent
{
TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls },
AllowPolling = { BindTarget = allowPolling }
}
}; };
private class MultiplayerListingPollingComponent : ListingPollingComponent private class MultiplayerListingPollingComponent : ListingPollingComponent
@ -146,26 +141,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll(); protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
} }
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();
}
} }
} }

View File

@ -8,6 +8,7 @@ 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.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -165,7 +166,10 @@ namespace osu.Game.Screens.OnlinePlay
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() => private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
{ {
if (state.NewValue != APIState.Online) if (state.NewValue != APIState.Online)
{
Logger.Log("API connection was lost, can't continue with online play", LoggingTarget.Network, LogLevel.Important);
Schedule(forcefullyExit); Schedule(forcefullyExit);
}
}); });
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -84,11 +84,11 @@ namespace osu.Game.Screens.Play.HUD
// BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually..
foreach (int userId in playingUsers) foreach (int userId in playingUsers)
{ {
if (!multiplayerClient.PlayingUsers.Contains(userId)) if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId))
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
} }
playingUsers.BindTo(multiplayerClient.PlayingUsers); playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
playingUsers.BindCollectionChanged(usersChanged); playingUsers.BindCollectionChanged(usersChanged);
} }

View File

@ -26,7 +26,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1228.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1229.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
<PackageReference Include="Sentry" Version="2.1.8" /> <PackageReference Include="Sentry" Version="2.1.8" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1228.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1229.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -88,7 +88,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1228.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1229.0" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />