diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index dd53eefd23..58c24181d3 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -15,7 +15,7 @@
]
},
"jetbrains.resharper.globaltools": {
- "version": "2020.2.4",
+ "version": "2020.3.2",
"commands": [
"jb"
]
diff --git a/Directory.Build.props b/Directory.Build.props
index 551cb75077..9ec442aafa 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,7 +16,7 @@
-
+
diff --git a/osu.Android.props b/osu.Android.props
index cd2ce58c55..611f0d05f4 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index 953c06f4e2..9d28ad7c5b 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -16,7 +16,9 @@ using osu.Framework.Android;
namespace osu.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
- [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataPathPatterns = new[] { ".*\\.osz", ".*\\.osk" }, DataMimeType = "application/*")]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity
{
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 61ecd79e3d..51d2032795 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index fa7bfd7169..3261f632f2 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index d6a03da807..32243e0bc3 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index a89645d881..210f81d111 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
index a3db20ce83..9a999a4931 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
@@ -8,7 +8,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
-using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
@@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual.Components
[Test]
public void TestNonPresentTrack()
{
- TestPreviewTrack track = null;
+ TestPreviewTrackManager.TestPreviewTrack track = null;
AddStep("get non-present track", () =>
{
@@ -182,9 +181,9 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
- private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
+ private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
- private TestPreviewTrack getOwnedTrack()
+ private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
index e1b0820662..5bac8582d7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
index 7a3845cbf3..80d1acd145 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
@@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
RoomManager =
{
TimeBetweenListingPolls = { Value = 1 },
- TimeBetweenSelectionPolls = { Value = 1 }
}
};
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 83d7b4135a..9049b67f90 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index bc6b994988..dc4f22788d 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs
index a1215d786b..4138c2757a 100644
--- a/osu.Game/Extensions/TaskExtensions.cs
+++ b/osu.Game/Extensions/TaskExtensions.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
+using System;
using System.Threading.Tasks;
+using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
namespace osu.Game.Extensions
@@ -13,13 +17,19 @@ namespace osu.Game.Extensions
/// Avoids unobserved exceptions from being fired.
///
/// The task.
- /// Whether errors should be logged as important, or silently ignored.
- public static void CatchUnobservedExceptions(this Task task, bool logOnError = false)
+ ///
+ /// Whether errors should be logged as errors visible to users, or as debug messages.
+ /// Logging as debug will essentially silence the errors on non-release builds.
+ ///
+ public static void CatchUnobservedExceptions(this Task task, bool logAsError = false)
{
task.ContinueWith(t =>
{
- if (logOnError)
- Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important);
+ Exception? exception = t.Exception?.AsSingular();
+ if (logAsError)
+ Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true);
+ else
+ Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug);
}, TaskContinuationOptions.NotOnRanToCompletion);
}
}
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 133ba22406..2aaea22155 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
@@ -293,8 +294,21 @@ namespace osu.Game.Online.API
failureCount = 0;
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)
{
+ log.Add($"{nameof(WebException)} while performing request {req}: {we.Message}");
handleWebException(we);
return false;
}
@@ -312,7 +326,7 @@ namespace osu.Game.Online.API
///
public IBindable State => state;
- private bool handleWebException(WebException we)
+ private void handleWebException(WebException we)
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
@@ -330,26 +344,24 @@ namespace osu.Game.Online.API
{
case HttpStatusCode.Unauthorized:
Logout();
- return true;
+ break;
case HttpStatusCode.RequestTimeout:
- failureCount++;
- log.Add($@"API failure count is now {failureCount}");
-
- if (failureCount < 3)
- // we might try again at an api level.
- return false;
-
- if (State.Value == APIState.Online)
- {
- state.Value = APIState.Failing;
- flushQueue();
- }
-
- return true;
+ handleFailure();
+ break;
}
+ }
- return true;
+ private void handleFailure()
+ {
+ failureCount++;
+ log.Add($@"API failure count is now {failureCount}");
+
+ if (failureCount >= 3 && State.Value == APIState.Online)
+ {
+ state.Value = APIState.Failing;
+ flushQueue();
+ }
}
public bool IsLoggedIn => localUser.Value.Id > 1;
diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs
index 780e5daa16..c8b76b9685 100644
--- a/osu.Game/Online/API/APIMod.cs
+++ b/osu.Game/Online/API/APIMod.cs
@@ -31,7 +31,12 @@ namespace osu.Game.Online.API
Acronym = mod.Acronym;
foreach (var (_, property) in mod.GetSettingsSourceProperties())
- Settings.Add(property.Name.Underscore(), property.GetValue(mod));
+ {
+ var bindable = (IBindable)property.GetValue(mod);
+
+ if (!bindable.IsDefault)
+ Settings.Add(property.Name.Underscore(), bindable);
+ }
}
public Mod ToMod(Ruleset ruleset)
@@ -46,7 +51,7 @@ namespace osu.Game.Online.API
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
- ((IBindable)property.GetValue(resultMod)).Parse(settingValue);
+ resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
}
return resultMod;
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 24ea6abc4a..7cd1ef78f7 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -88,11 +88,12 @@ namespace osu.Game.Online.Multiplayer
{
isConnected.Value = false;
- if (ex != null)
- {
- Logger.Log($"Multiplayer client lost connection: {ex}", LoggingTarget.Network);
+ Logger.Log(ex != null
+ ? $"Multiplayer client lost connection: {ex}"
+ : "Multiplayer client disconnected", LoggingTarget.Network);
+
+ if (connection != null)
await tryUntilConnected();
- }
};
await tryUntilConnected();
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
index 2134e50d72..12fcf25ace 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
@@ -5,9 +5,7 @@
using System;
using System.Collections.Generic;
-using System.Threading;
using Newtonsoft.Json;
-using osu.Framework.Allocation;
namespace osu.Game.Online.Multiplayer
{
@@ -42,35 +40,12 @@ namespace osu.Game.Online.Multiplayer
///
public MultiplayerRoomUser? Host { get; set; }
- private object writeLock = new object();
-
[JsonConstructor]
public MultiplayerRoom(in long roomId)
{
RoomID = roomId;
}
- private object updateLock = new object();
-
- private ManualResetEventSlim freeForWrite = new ManualResetEventSlim(true);
-
- ///
- /// Request a lock on this room to perform a thread-safe update.
- ///
- public IDisposable LockForUpdate()
- {
- // ReSharper disable once InconsistentlySynchronizedField
- freeForWrite.Wait();
-
- lock (updateLock)
- {
- freeForWrite.Wait();
- freeForWrite.Reset();
-
- return new ValueInvokeOnDisposal(this, r => freeForWrite.Set());
- }
- }
-
public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]";
}
}
diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
index 5c6baa4fea..34cba09e8c 100644
--- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Online.Multiplayer
IsConnected.BindValueChanged(connected =>
{
// 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);
LeaveRoom().CatchUnobservedExceptions();
diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
index 6bbff045b5..aa36a5c8fd 100644
--- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
@@ -7,12 +7,11 @@ namespace osu.Game.Overlays.Changelog
{
public class ChangelogUpdateStreamControl : OverlayStreamControl
{
- protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value);
-
- protected override void LoadComplete()
+ public ChangelogUpdateStreamControl()
{
- // suppress base logic of immediately selecting first item if one exists
- // (we always want to start with no stream selected).
+ SelectFirstTabByDefault = false;
}
+
+ protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value);
}
}
diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs
index e2d4fc6051..7999023998 100644
--- a/osu.Game/PerformFromMenuRunner.cs
+++ b/osu.Game/PerformFromMenuRunner.cs
@@ -73,15 +73,19 @@ namespace osu.Game
// find closest valid target
IScreen current = getCurrentScreen();
+ if (current == null)
+ return;
+
// a dialog may be blocking the execution for now.
if (checkForDialog(current)) return;
game?.CloseAllOverlays(false);
// we may already be at the target screen type.
- if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled)
+ if (validScreens.Contains(current.GetType()) && !beatmap.Disabled)
{
- complete();
+ finalAction(current);
+ Cancel();
return;
}
@@ -135,11 +139,5 @@ namespace osu.Game
lastEncounteredDialogScreen = current;
return true;
}
-
- private void complete()
- {
- finalAction(getCurrentScreen());
- Cancel();
- }
}
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index b8dc7a2661..24d184e531 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
@@ -84,12 +83,10 @@ namespace osu.Game.Rulesets.Mods
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
{
- object bindableObj = property.GetValue(this);
+ var bindable = (IBindable)property.GetValue(this);
- if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
- continue;
-
- tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ if (!bindable.IsDefault)
+ tooltipTexts.Add($"{attr.Label} {bindable}");
}
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
@@ -136,19 +133,38 @@ namespace osu.Game.Rulesets.Mods
// Copy bindable values across
foreach (var (_, prop) in this.GetSettingsSourceProperties())
{
- var origBindable = prop.GetValue(this);
- var copyBindable = prop.GetValue(copy);
+ var origBindable = (IBindable)prop.GetValue(this);
+ var copyBindable = (IBindable)prop.GetValue(copy);
- // The bindables themselves are readonly, so the value must be transferred through the Bindable.Value property.
- var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable
-
+
@@ -88,7 +88,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 22ea73858e..aa8f8739c1 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -106,6 +106,7 @@
HINT
WARNING
WARNING
+ DO_NOT_SHOW
WARNING
WARNING
WARNING