From 2ea90ef98abebc68d80724b9a1964d30cc4df2cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Aug 2018 19:25:55 +0900 Subject: [PATCH 01/18] Add sentry logging --- osu.Desktop/Program.cs | 22 +++++++ osu.Game/OsuGame.cs | 16 +++++ osu.Game/Screens/Select/PlaySongSelect.cs | 3 + osu.Game/Utils/RavenLogger.cs | 73 +++++++++++++++++++++++ osu.Game/osu.Game.csproj | 3 +- 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Utils/RavenLogger.cs diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cc08e08653..4574fb6fc9 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -4,7 +4,10 @@ using System; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using osu.Framework; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IPC; @@ -20,6 +23,8 @@ namespace osu.Desktop using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) { + host.ExceptionThrown += handleException; + if (!host.IsPrimaryInstance) { var importer = new ArchiveImportIPCChannel(host); @@ -45,5 +50,22 @@ namespace osu.Desktop return 0; } } + + private static int allowableExceptions = 1; + + /// + /// Allow a maximum of one unhandled exception, per second of execution. + /// + /// + /// + private static bool handleException(Exception arg) + { + bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; + + Logger.Log($"Unhandled exception has been {(continueExecution ? "allowed" : "denied")} with {allowableExceptions} more allowable exceptions."); + + Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); + return Interlocked.Decrement(ref allowableExceptions) >= 0; + } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 025d5f50e3..3e6317348c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -36,6 +36,8 @@ using osu.Game.Skinning; using OpenTK.Graphics; using osu.Game.Overlays.Volume; using osu.Game.Screens.Select; +using osu.Game.Utils; +using LogLevel = osu.Framework.Logging.LogLevel; namespace osu.Game { @@ -65,6 +67,8 @@ namespace osu.Game private ScreenshotManager screenshotManager; + protected RavenLogger RavenLogger; + public virtual Storage GetStorageForStableInstall() => null; private Intro intro @@ -106,6 +110,8 @@ namespace osu.Game this.args = args; forwardLoggedErrorsToNotifications(); + + RavenLogger = new RavenLogger(this); } public void ToggleSettings() => settings.ToggleVisibility(); @@ -150,6 +156,8 @@ namespace osu.Game dependencies.CacheAs(this); + dependencies.Cache(RavenLogger); + dependencies.CacheAs(ruleset); dependencies.CacheAs>(ruleset); @@ -271,6 +279,12 @@ namespace osu.Game menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay))); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + RavenLogger.Dispose(); + } + protected override void LoadComplete() { // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer. @@ -599,6 +613,7 @@ namespace osu.Game private void screenAdded(Screen newScreen) { currentScreen = (OsuScreen)newScreen; + Logger.Log($"Screen changed → {currentScreen}"); newScreen.ModePushed += screenAdded; newScreen.Exited += screenRemoved; @@ -607,6 +622,7 @@ namespace osu.Game private void screenRemoved(Screen newScreen) { currentScreen = (OsuScreen)newScreen; + Logger.Log($"Screen changed ← {currentScreen}"); if (newScreen == null) Exit(); diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index a346911ca2..a73254627d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -141,6 +142,8 @@ namespace osu.Game.Screens.Select { if (player != null) return false; + throw new Exception("this is a test!"); + // Ctrl+Enter should start map with autoplay enabled. if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs new file mode 100644 index 0000000000..aadb70add8 --- /dev/null +++ b/osu.Game/Utils/RavenLogger.cs @@ -0,0 +1,73 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using osu.Framework.Logging; +using SharpRaven; +using SharpRaven.Data; + +namespace osu.Game.Utils +{ + /// + /// Report errors to sentry. + /// + public class RavenLogger : IDisposable + { + private readonly RavenClient raven = new RavenClient("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"); + + private readonly List tasks = new List(); + + public RavenLogger(OsuGame game) + { + raven.Release = game.Version; + + Logger.NewEntry += entry => + { + if (entry.Level < LogLevel.Verbose) return; + + if (entry.Exception != null) + queuePendingTask(raven.CaptureAsync(new SentryEvent(entry.Exception))); + else + raven.AddTrail(new Breadcrumb(entry.Target.ToString(), BreadcrumbType.Navigation) { Message = entry.Message }); + }; + } + + private void queuePendingTask(Task task) + { + lock (tasks) tasks.Add(task); + task.ContinueWith(_ => + { + lock (tasks) + tasks.Remove(task); + }); + } + + #region Disposal + + ~RavenLogger() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected virtual void Dispose(bool isDisposing) + { + if (isDisposed) + return; + + isDisposed = true; + lock (tasks) Task.WaitAll(tasks.ToArray(), 5000); + } + + #endregion + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 89e80bd06b..9a7edb6ae2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,9 +18,10 @@ - + + \ No newline at end of file From b26c8e3b9e85c09c908332c52704aa552a2170e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 14:02:49 +0900 Subject: [PATCH 02/18] Fix notifiation stack trace output on mania conversion failure --- .../Beatmaps/Patterns/PatternGenerator.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index 3434c9f01e..1f416f921e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -55,15 +55,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns { int iterations = 0; - while (condition() && iterations++ < max_rng_iterations) + while (condition()) + { + if (iterations++ >= max_rng_iterations) + { + // log an error but don't throw. we want to continue execution. + Logger.Error(new ExceededAllowedIterationsException(new StackTrace(0)), + "Conversion encountered errors. The beatmap may not be correctly converted."); + return; + } + action(); - - if (iterations < max_rng_iterations) - return; - - // Generate + log an error/stacktrace - - Logger.Log($"Allowable iterations ({max_rng_iterations}) exceeded:\n{new StackTrace(0)}", level: LogLevel.Error); + } } /// @@ -71,5 +74,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// /// The s containing the hit objects. public abstract IEnumerable Generate(); + + /// + /// Denotes when a single conversion operation is in an infinitely looping state. + /// + public class ExceededAllowedIterationsException : Exception + { + private readonly string stackTrace; + + public ExceededAllowedIterationsException(StackTrace stackTrace) + { + this.stackTrace = stackTrace.ToString(); + } + + public override string StackTrace => stackTrace; + } } } From 562a31713e04f23f23d1596bf74b09dad658c16b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 16:44:04 +0900 Subject: [PATCH 03/18] Fix regression in handling logic --- osu.Desktop/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 4574fb6fc9..19ceade644 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -65,7 +65,7 @@ namespace osu.Desktop Logger.Log($"Unhandled exception has been {(continueExecution ? "allowed" : "denied")} with {allowableExceptions} more allowable exceptions."); Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); - return Interlocked.Decrement(ref allowableExceptions) >= 0; + return continueExecution; } } } From 9f55afe5692a91368df55f37e279630ceb5b78b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 16:44:12 +0900 Subject: [PATCH 04/18] Let user know automatic reporting happened --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 189a51dec9..6576529326 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -463,7 +463,7 @@ namespace osu.Game Schedule(() => notifications.Post(new SimpleNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, - Text = entry.Message, + Text = entry.Message + (entry.Exception != null ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } else if (recentLogCount == short_term_display_limit) From 6297ad95aa49c133c860f59d7c6cbeabbc9ad1eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 16:44:18 +0900 Subject: [PATCH 05/18] Remove test exception --- osu.Game/Screens/Select/PlaySongSelect.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e83097637d..e914eb365e 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -143,8 +142,6 @@ namespace osu.Game.Screens.Select { if (player != null) return false; - throw new Exception("this is a test!"); - // Ctrl+Enter should start map with autoplay enabled. if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { From dfecb3235b7d3d0bbf0d694f1528071aa87e61b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 20:41:53 +0900 Subject: [PATCH 06/18] Fix custom exception stack trace output --- osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index 1f416f921e..1eb0cdae2f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -88,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns } public override string StackTrace => stackTrace; + public override string ToString() => $"{GetType().Name}: {Message}\r\n{StackTrace}"; } } } From 12b81df2f39855a916b6ce9d151724361e175872 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Aug 2018 22:01:04 +0900 Subject: [PATCH 07/18] Fix unhandled exception on startup when arguments are present --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6576529326..ecf5500ca4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -151,9 +151,9 @@ namespace osu.Game if (args?.Length > 0) { - var paths = args.Where(a => !a.StartsWith(@"-")); - - Task.Run(() => Import(paths.ToArray())); + var paths = args.Where(a => !a.StartsWith(@"-")).ToArray(); + if (paths.Length > 0) + Task.Run(() => Import(paths)); } dependencies.CacheAs(this); From 9f5a6457f26be725988c1e29d9641a24d7c69b07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Aug 2018 09:08:58 +0900 Subject: [PATCH 08/18] Don't forward errors to sentry for local builds --- osu.Game/Utils/RavenLogger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index aadb70add8..761bd51672 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -23,6 +23,8 @@ namespace osu.Game.Utils { raven.Release = game.Version; + if (!game.IsDeployedBuild) return; + Logger.NewEntry += entry => { if (entry.Level < LogLevel.Verbose) return; From eb6f1ae72cda9baa2dc4b903724932a511163743 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 09:16:28 +0900 Subject: [PATCH 09/18] Fix spinners providing one extra combo --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 -- .../Legacy/Catch/ConvertHitObjectParser.cs | 22 ++++++++++++++++--- .../Legacy/Osu/ConvertHitObjectParser.cs | 22 ++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index e1a7a7c6df..1c60fd4831 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int SpinsRequired { get; protected set; } = 1; - public override bool NewCombo => true; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index fb4cde479b..cb44fb1c8c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -18,8 +18,17 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch { } + private bool forceNewCombo; + private int extraComboOffset; + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { + newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + + forceNewCombo = false; + extraComboOffset = 0; + return new ConvertHit { X = position.X, @@ -30,6 +39,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { + newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + + forceNewCombo = false; + extraComboOffset = 0; + return new ConvertSlider { X = position.X, @@ -45,11 +60,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { + forceNewCombo |= FormatVersion <= 8 || newCombo; + extraComboOffset += comboOffset; + return new ConvertSpinner { - EndTime = endTime, - NewCombo = FirstObject || newCombo, - ComboOffset = comboOffset + EndTime = endTime }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 0823653830..497d85f849 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -19,8 +19,17 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu { } + private bool forceNewCombo; + private int extraComboOffset; + protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset) { + newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + + forceNewCombo = false; + extraComboOffset = 0; + return new ConvertHit { Position = position, @@ -31,6 +40,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) { + newCombo |= forceNewCombo; + comboOffset += extraComboOffset; + + forceNewCombo = false; + extraComboOffset = 0; + return new ConvertSlider { Position = position, @@ -46,12 +61,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { + forceNewCombo |= FormatVersion <= 8 || newCombo; + extraComboOffset += comboOffset; + return new ConvertSpinner { Position = position, - EndTime = endTime, - NewCombo = FormatVersion <= 8 || FirstObject || newCombo, - ComboOffset = comboOffset + EndTime = endTime }; } From 557a2ee39d42937b5f0501ae147a86da66e45dc3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 10:04:00 +0900 Subject: [PATCH 10/18] Add more comprehensive tests --- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- .../Beatmaps/OsuBeatmapProcessor.cs | 2 +- .../Formats/LegacyBeatmapDecoderTest.cs | 38 ++++++++++++++++++- .../Resources/hitobject-combo-offset.osu | 30 ++++++++++++++- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 93f3f06dc2..9e0e649eb2 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Osu.Beatmaps { - internal class OsuBeatmapConverter : BeatmapConverter + public class OsuBeatmapConverter : BeatmapConverter { public OsuBeatmapConverter(IBeatmap beatmap) : base(beatmap) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index bbe2d67baa..5fe2457645 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Beatmaps { - internal class OsuBeatmapProcessor : BeatmapProcessor + public class OsuBeatmapProcessor : BeatmapProcessor { public OsuBeatmapProcessor(IBeatmap beatmap) : base(beatmap) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 0a5df0e093..d3351f86f8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -11,7 +11,9 @@ using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Skinning; namespace osu.Game.Tests.Beatmaps.Formats @@ -187,14 +189,46 @@ namespace osu.Game.Tests.Beatmaps.Formats } [Test] - public void TestDecodeBeatmapComboOffsets() + public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); - Assert.AreEqual(3, ((IHasCombo)beatmap.HitObjects[0]).ComboOffset); + + var converted = new OsuBeatmapConverter(beatmap).Convert(); + new OsuBeatmapProcessor(converted).PreProcess(); + new OsuBeatmapProcessor(converted).PostProcess(); + + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex); + } + } + + [Test] + public void TestDecodeBeatmapComboOffsetsCatch() + { + var decoder = new LegacyBeatmapDecoder(); + using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu")) + using (var stream = new StreamReader(resStream)) + { + var beatmap = decoder.Decode(stream); + + var converted = new CatchBeatmapConverter(beatmap).Convert(); + new CatchBeatmapProcessor(converted).PreProcess(); + new CatchBeatmapProcessor(converted).PostProcess(); + + Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex); + Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex); + Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex); + Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex); + Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex); } } diff --git a/osu.Game.Tests/Resources/hitobject-combo-offset.osu b/osu.Game.Tests/Resources/hitobject-combo-offset.osu index 4a44d31e22..c1f0dab8e9 100644 --- a/osu.Game.Tests/Resources/hitobject-combo-offset.osu +++ b/osu.Game.Tests/Resources/hitobject-combo-offset.osu @@ -1,4 +1,32 @@ osu file format v14 [HitObjects] -255,193,2170,49,0,0:0:0:0: \ No newline at end of file +// Circle with combo offset (3) +255,193,1000,49,0,0:0:0:0: +// Combo index = 4 + +// Slider with new combo followed by circle with no new combo +256,192,2000,12,0,2000,0:0:0:0: +255,193,3000,1,0,0:0:0:0: +// Combo index = 5 + +// Slider without new combo followed by circle with no new combo +256,192,4000,8,0,5000,0:0:0:0: +255,193,6000,1,0,0:0:0:0: +// Combo index = 5 + +// Slider without new combo followed by circle with new combo +256,192,7000,8,0,8000,0:0:0:0: +255,193,9000,5,0,0:0:0:0: +// Combo index = 6 + +// Slider with new combo and offset (1) followed by circle with new combo and offset (3) +256,192,10000,28,0,11000,0:0:0:0: +255,193,12000,53,0,0:0:0:0: +// Combo index = 11 + +// Slider with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo +256,192,13000,44,0,14000,0:0:0:0: +256,192,15000,8,0,16000,0:0:0:0: +255,193,17000,1,0,0:0:0:0: +// Combo index = 14 \ No newline at end of file From 83dc01d07c9d61523e294d1affa9763c448b96b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Aug 2018 12:03:31 +0900 Subject: [PATCH 11/18] Fix multiple sentry reports arriving for similar exceptions --- osu.Desktop/Program.cs | 4 +++- osu.Game/Utils/RavenLogger.cs | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 19ceade644..db77427f96 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -62,9 +62,11 @@ namespace osu.Desktop { bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; - Logger.Log($"Unhandled exception has been {(continueExecution ? "allowed" : "denied")} with {allowableExceptions} more allowable exceptions."); + Logger.Log($"Unhandled exception has been {(continueExecution ? "allowed with {allowableExceptions} more allowable exceptions" : "denied")} ."); + // restore the stock of allowable exceptions after a short delay. Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions)); + return continueExecution; } } diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 761bd51672..70080fefa8 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -19,18 +19,30 @@ namespace osu.Game.Utils private readonly List tasks = new List(); + private Exception lastException; + public RavenLogger(OsuGame game) { raven.Release = game.Version; - if (!game.IsDeployedBuild) return; - Logger.NewEntry += entry => { if (entry.Level < LogLevel.Verbose) return; - if (entry.Exception != null) - queuePendingTask(raven.CaptureAsync(new SentryEvent(entry.Exception))); + var exception = entry.Exception; + + if (exception != null) + { + // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. + if (lastException != null && + lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) + { + return; + } + + lastException = exception; + queuePendingTask(raven.CaptureAsync(new SentryEvent(exception))); + } else raven.AddTrail(new Breadcrumb(entry.Target.ToString(), BreadcrumbType.Navigation) { Message = entry.Message }); }; From 0ae487b9c9a478afff2e083a07475a2da44932c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Aug 2018 12:27:36 +0900 Subject: [PATCH 12/18] Add back missing statement --- osu.Game/Utils/RavenLogger.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index 70080fefa8..b28dd1fb73 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -25,6 +25,8 @@ namespace osu.Game.Utils { raven.Release = game.Version; + if (!game.IsDeployedBuild) return; + Logger.NewEntry += entry => { if (entry.Level < LogLevel.Verbose) return; From 99ace9805f25c54e164b07979ed0e16a4a54fcfe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 12:54:32 +0900 Subject: [PATCH 13/18] Fix mirror note generation never completing --- .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 930ca26660..5860480a91 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -286,6 +286,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The containing the hit objects. private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) { + if (convertType.HasFlag(PatternType.ForceNotStack)) + return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); + var pattern = new Pattern(); bool addToCentre; @@ -370,9 +373,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { addToCentre = false; - if (convertType.HasFlag(PatternType.ForceNotStack)) - return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); - switch (TotalColumns) { case 2: From 7a8aabc402acc6ca16b8d7dafa2419be4354623d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Aug 2018 13:18:48 +0900 Subject: [PATCH 14/18] Fix notifications suggesting automatic reporting in non-deployed builds --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ecf5500ca4..d54bdee1b2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -463,7 +463,7 @@ namespace osu.Game Schedule(() => notifications.Post(new SimpleNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, - Text = entry.Message + (entry.Exception != null ? "\n\nThis error has been automatically reported to the devs." : string.Empty), + Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } else if (recentLogCount == short_term_display_limit) From 3d6721111a8159a0a6ad9bebb9ded491cd952cb8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 13:26:53 +0900 Subject: [PATCH 15/18] Increase the maximum allowable iterations during mania conversion --- osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index 1eb0cdae2f..e51cbcdc60 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -18,8 +18,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// /// An arbitrary maximum amount of iterations to perform in . /// The specific value is not super important - enough such that no false-positives occur. + /// + /// /b/933228 requires at least 23 iterations. /// - private const int max_rng_iterations = 20; + private const int max_rng_iterations = 30; /// /// The last pattern. From 583e026906c46b5861bf30a3626dadc840fb5c13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Aug 2018 13:28:35 +0900 Subject: [PATCH 16/18] =?UTF-8?q?Don=E2=80=99t=20suppress=20unhandled=20ex?= =?UTF-8?q?ceptions=20in=20debug=20builds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- osu.Desktop/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index db77427f96..71613753bc 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; +using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IPC; @@ -51,7 +52,7 @@ namespace osu.Desktop } } - private static int allowableExceptions = 1; + private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1; /// /// Allow a maximum of one unhandled exception, per second of execution. From 16d30f6756d78cf9f38ac3e293c870e5b8653b0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 13:31:12 +0900 Subject: [PATCH 17/18] Add explanatory comments --- .../Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs | 2 ++ osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index cb44fb1c8c..802080aedb 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { + // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo + // Their combo offset is still added to that next hitobject's combo index forceNewCombo |= FormatVersion <= 8 || newCombo; extraComboOffset += comboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index 497d85f849..acd0de8688 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) { + // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo + // Their combo offset is still added to that next hitobject's combo index forceNewCombo |= FormatVersion <= 8 || newCombo; extraComboOffset += comboOffset; From 83bda313d1dc092332a71e4320d6285480fe5404 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 17 Aug 2018 13:50:27 +0900 Subject: [PATCH 18/18] Output the currently importing model --- osu.Game/Database/ArchiveModelManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index c00df59e3e..ac79a8f565 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -198,6 +198,8 @@ namespace osu.Game.Database try { + Logger.Log($"Importing {item}...", LoggingTarget.Database); + using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { try