From 15b533f2a4dcf427b9ddfe5838e237427a7f0bbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 15:06:10 +0900 Subject: [PATCH 01/39] Hash skins based on name, not skin.ini contents It is feasible that a user may be changing the contents of skin.ini without changing the skin name / author. Such changes should not create a new skin if already imported. --- osu.Game/Database/ArchiveModelManager.cs | 10 +++++++--- osu.Game/Skinning/SkinManager.cs | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 49d7edd56c..e87ab8167a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -253,6 +253,9 @@ namespace osu.Game.Database /// Generally should include all file types which determine the file's uniqueness. /// Large files should be avoided if possible. /// + /// + /// This is only used by the default hash implementation. If is overridden, it will not be used. + /// protected abstract string[] HashableFileTypes { get; } internal static void LogForModel(TModel model, string message, Exception e = null) @@ -271,7 +274,7 @@ namespace osu.Game.Database /// /// In the case of no matching files, a hash will be generated from the passed archive's . /// - private string computeHash(TModel item, ArchiveReader reader = null) + protected virtual string ComputeHash(TModel item, ArchiveReader reader = null) { // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); @@ -318,10 +321,11 @@ namespace osu.Game.Database LogForModel(item, "Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); - item.Hash = computeHash(item, archive); await Populate(item, archive, cancellationToken); + item.Hash = ComputeHash(item, archive); + using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { try @@ -437,7 +441,7 @@ namespace osu.Game.Database { using (ContextFactory.GetForWrite()) { - item.Hash = computeHash(item); + item.Hash = ComputeHash(item); ModelStore.Update(item); } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e1f713882a..46130cbdd4 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -12,6 +12,7 @@ using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; @@ -86,6 +87,13 @@ namespace osu.Game.Skinning protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) + { + // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. + // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). + return item.ToString().ComputeSHA2Hash(); + } + protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { await base.Populate(model, archive, cancellationToken); From 62e5c9d2636cd1b21b064633471e0e6e9c4844d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:20:30 +0900 Subject: [PATCH 02/39] Add test coverage --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 170 ++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Tests/Skins/IO/ImportSkinTest.cs diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs new file mode 100644 index 0000000000..af38d0f3c4 --- /dev/null +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -0,0 +1,170 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.IO.Archives; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; +using SharpCompress.Archives.Zip; + +namespace osu.Game.Tests.Skins.IO +{ + public class ImportSkinTest + { + [Test] + public async Task TestBasicImport() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + + Assert.That(imported.Name, Is.EqualTo("test skin")); + Assert.That(imported.Creator, Is.EqualTo("skinner")); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportTwiceWithSameMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(1)); + + // the first should be overwritten by the second import. + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportTwiceWithDifferentMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); + + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + + private MemoryStream createOsk(string name, string author) + { + var zipStream = new MemoryStream(); + using var zip = ZipArchive.Create(); + zip.AddEntry("skin.ini", generateSkinIni(name, author)); + zip.SaveTo(zipStream); + return zipStream; + } + + private MemoryStream generateSkinIni(string name, string author) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + + writer.WriteLine("[General]"); + writer.WriteLine($"Name: {name}"); + writer.WriteLine($"Author: {author}"); + writer.WriteLine(); + writer.WriteLine($"# unique {Guid.NewGuid()}"); + + writer.Flush(); + + return stream; + } + + private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + { + var beatmapManager = osu.Dependencies.Get(); + + var skinManager = osu.Dependencies.Get(); + return await skinManager.Import(archive); + } + + private async Task loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + +#pragma warning disable 4014 + Task.Run(() => host.Run(osu)); +#pragma warning restore 4014 + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + var beatmapFile = TestResources.GetTestBeatmapForImport(); + var beatmapManager = osu.Dependencies.Get(); + await beatmapManager.Import(beatmapFile); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + + private class TestArchiveReader : ArchiveReader + { + public TestArchiveReader() + : base("test_archive") + { + } + + public override Stream GetStream(string name) => new MemoryStream(); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { "test_file.osr" }; + } + } +} From ef77658311f2d7471e1bcbc56f252862131e1615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:29:14 +0900 Subject: [PATCH 03/39] Add coverage of case where skin.ini doesn't specify name/author --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 26 +++++++++++++++++++++++ osu.Game/Skinning/SkinManager.cs | 16 ++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index af38d0f3c4..ad1a41a2b3 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -66,6 +66,32 @@ namespace osu.Game.Tests.Skins.IO } } + [Test] + public async Task TestImportTwiceWithNoMetadata() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + { + try + { + var osu = await loadOsu(host); + + // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. + var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + + Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); + + Assert.That(osu.Dependencies.Get().GetAllUserSkins().First().Files.First().FileInfoID, Is.EqualTo(imported.Files.First().FileInfoID)); + Assert.That(osu.Dependencies.Get().GetAllUserSkins().Last().Files.First().FileInfoID, Is.EqualTo(imported2.Files.First().FileInfoID)); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportTwiceWithDifferentMetadata() { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 46130cbdd4..eacfdaec4a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -87,11 +87,19 @@ namespace osu.Game.Skinning protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + private const string unknown_creator_string = "Unknown"; + protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { - // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. - // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). - return item.ToString().ComputeSHA2Hash(); + if (item.Creator != null && item.Creator != unknown_creator_string) + { + // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. + // likely, the skin should expose a real version (ie. the version of the skin, not the skin.ini version it's targeting). + return item.ToString().ComputeSHA2Hash(); + } + + // if there was no creator, the ToString above would give the filename, which along isn't really enough to base any decisions on. + return base.ComputeHash(item, reader); } protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) @@ -108,7 +116,7 @@ namespace osu.Game.Skinning else { model.Name = model.Name.Replace(".osk", ""); - model.Creator ??= "Unknown"; + model.Creator ??= unknown_creator_string; } } From 948437865b32bafbe7af9df7c51c675f041e30d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Sep 2020 16:42:11 +0900 Subject: [PATCH 04/39] Remove unused code --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ad1a41a2b3..14eff4c5e3 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -144,8 +143,6 @@ namespace osu.Game.Tests.Skins.IO private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { - var beatmapManager = osu.Dependencies.Get(); - var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); } @@ -176,21 +173,5 @@ namespace osu.Game.Tests.Skins.IO Assert.IsTrue(task.Wait(timeout), failureMessage); } - - private class TestArchiveReader : ArchiveReader - { - public TestArchiveReader() - : base("test_archive") - { - } - - public override Stream GetStream(string name) => new MemoryStream(); - - public override void Dispose() - { - } - - public override IEnumerable Filenames => new[] { "test_file.osr" }; - } } } From 91d37e0459e37f1caacfb7c623b05cd83d454848 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 20:17:00 +0900 Subject: [PATCH 05/39] Fix typo in comment --- osu.Game/Skinning/SkinManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index eacfdaec4a..303c59b05e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -98,7 +98,7 @@ namespace osu.Game.Skinning return item.ToString().ComputeSHA2Hash(); } - // if there was no creator, the ToString above would give the filename, which along isn't really enough to base any decisions on. + // if there was no creator, the ToString above would give the filename, which alone isn't really enough to base any decisions on. return base.ComputeHash(item, reader); } From 1884e0167bdb5bfa1d2769e00874520b39af8c71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Sep 2020 23:31:03 +0900 Subject: [PATCH 06/39] Eagerly populate skin metadata to allow usage in hashing computation --- osu.Game/Database/ArchiveModelManager.cs | 3 +-- osu.Game/Skinning/SkinManager.cs | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e87ab8167a..76bc4f7755 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -321,11 +321,10 @@ namespace osu.Game.Database LogForModel(item, "Beginning import..."); item.Files = archive != null ? createFileInfos(archive, Files) : new List(); + item.Hash = ComputeHash(item, archive); await Populate(item, archive, cancellationToken); - item.Hash = ComputeHash(item, archive); - using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { try diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 303c59b05e..ee4b7bc8e7 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -91,6 +91,10 @@ namespace osu.Game.Skinning protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { + // we need to populate early to create a hash based off skin.ini contents + if (item.Name?.Contains(".osk") == true) + populateMetadata(item); + if (item.Creator != null && item.Creator != unknown_creator_string) { // this is the optimal way to hash legacy skins, but will need to be reconsidered when we move forward with skin implementation. @@ -106,17 +110,23 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken); - Skin reference = GetSkin(model); + if (model.Name?.Contains(".osk") == true) + populateMetadata(model); + } + + private void populateMetadata(SkinInfo item) + { + Skin reference = GetSkin(item); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { - model.Name = reference.Configuration.SkinInfo.Name; - model.Creator = reference.Configuration.SkinInfo.Creator; + item.Name = reference.Configuration.SkinInfo.Name; + item.Creator = reference.Configuration.SkinInfo.Creator; } else { - model.Name = model.Name.Replace(".osk", ""); - model.Creator ??= unknown_creator_string; + item.Name = item.Name.Replace(".osk", ""); + item.Creator ??= unknown_creator_string; } } From 15e423157b4c49db9764ad99162341f11a37110c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:01:29 +0900 Subject: [PATCH 07/39] Fix tests that access LocalStorage before BDL --- .../TestSceneManageCollectionsDialog.cs | 27 +++++++------------ .../SongSelect/TestSceneFilterControl.cs | 22 +++++---------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 54ab20af7f..55b61bc54a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -22,44 +22,35 @@ namespace osu.Game.Tests.Visual.Collections { public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene { - protected override Container Content => content; + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private readonly Container content; - private readonly DialogOverlay dialogOverlay; - private readonly CollectionManager manager; + private DialogOverlay dialogOverlay; + private CollectionManager manager; private RulesetStore rulesets; private BeatmapManager beatmapManager; private ManageCollectionsDialog dialog; - public TestSceneManageCollectionsDialog() + [BackgroundDependencyLoader] + private void load(GameHost host) { base.Content.AddRange(new Drawable[] { manager = new CollectionManager(LocalStorage), - content = new Container { RelativeSizeAxes = Axes.Both }, + Content, dialogOverlay = new DialogOverlay() }); - } - [BackgroundDependencyLoader] - private void load(GameHost host) - { + Dependencies.Cache(manager); + Dependencies.Cache(dialogOverlay); + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(manager); - dependencies.Cache(dialogOverlay); - return dependencies; - } - [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 7cd4791acb..0f03368296 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -23,41 +23,31 @@ namespace osu.Game.Tests.Visual.SongSelect { public class TestSceneFilterControl : OsuManualInputManagerTestScene { - protected override Container Content => content; - private readonly Container content; + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private readonly CollectionManager collectionManager; + private CollectionManager collectionManager; private RulesetStore rulesets; private BeatmapManager beatmapManager; private FilterControl control; - public TestSceneFilterControl() + [BackgroundDependencyLoader] + private void load(GameHost host) { base.Content.AddRange(new Drawable[] { collectionManager = new CollectionManager(LocalStorage), - content = new Container { RelativeSizeAxes = Axes.Both } + Content }); - } - [BackgroundDependencyLoader] - private void load(GameHost host) - { + Dependencies.Cache(collectionManager); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(collectionManager); - return dependencies; - } - [SetUp] public void SetUp() => Schedule(() => { From 234152b2fe6a886051acf5aea1d49edaf343bac9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:03:36 +0900 Subject: [PATCH 08/39] Use host storage as LocalStorage for headless test runs --- osu.Game/Tests/Visual/OsuTestScene.cs | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4db5139813..6286809595 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual private Lazy localStorage; protected Storage LocalStorage => localStorage.Value; - private readonly Lazy contextFactory; + private Lazy contextFactory; protected IAPIProvider API { @@ -71,6 +71,17 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + contextFactory = new Lazy(() => + { + var factory = new DatabaseContextFactory(LocalStorage); + factory.ResetDatabase(); + using (var usage = factory.Get()) + usage.Migrate(); + return factory; + }); + + RecycleLocalStorage(); + var baseDependencies = base.CreateChildDependencies(parent); var providedRuleset = CreateRuleset(); @@ -104,16 +115,6 @@ namespace osu.Game.Tests.Visual protected OsuTestScene() { - RecycleLocalStorage(); - contextFactory = new Lazy(() => - { - var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); - using (var usage = factory.Get()) - usage.Migrate(); - return factory; - }); - base.Content.Add(content = new DrawSizePreservingFillContainer()); } @@ -131,9 +132,14 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + localStorage = host is HeadlessGameHost + ? new Lazy(() => host.Storage) + : new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } + [Resolved] + private GameHost host { get; set; } + [Resolved] protected AudioManager Audio { get; private set; } @@ -172,7 +178,7 @@ namespace osu.Game.Tests.Visual if (MusicController?.TrackLoaded == true) MusicController.CurrentTrack.Stop(); - if (contextFactory.IsValueCreated) + if (contextFactory?.IsValueCreated == true) contextFactory.Value.ResetDatabase(); RecycleLocalStorage(); From 879979ef57f4b8139c1f37d117f2229d84f3d45b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 14:25:31 +0900 Subject: [PATCH 09/39] Move host lookup to inside lazy retrieval to handle edge cases --- osu.Game/Tests/Visual/OsuTestScene.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6286809595..611bc8f30f 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -132,9 +132,8 @@ namespace osu.Game.Tests.Visual } } - localStorage = host is HeadlessGameHost - ? new Lazy(() => host.Storage) - : new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + localStorage = + new Lazy(() => host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From 3242b10187f77e55f171e71610e17026187e30e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 15:00:04 +0900 Subject: [PATCH 10/39] Change order of dependency caching to promote use of locals --- .../Collections/TestSceneManageCollectionsDialog.cs | 10 +++++----- .../Visual/SongSelect/TestSceneFilterControl.cs | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 55b61bc54a..fef1605f0c 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -35,6 +35,11 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + base.Content.AddRange(new Drawable[] { manager = new CollectionManager(LocalStorage), @@ -44,11 +49,6 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(manager); Dependencies.Cache(dialogOverlay); - - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } [SetUp] diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 0f03368296..5d0fb248df 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -35,6 +35,11 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); + + beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + base.Content.AddRange(new Drawable[] { collectionManager = new CollectionManager(LocalStorage), @@ -42,10 +47,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); Dependencies.Cache(collectionManager); - Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); } [SetUp] From f7c9c805665152a526f9b67b48281fc51ca7e4ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 19:01:32 +0900 Subject: [PATCH 11/39] Force OsuGameTests to use a unique storage each run --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 2 ++ osu.Game/Tests/Visual/OsuTestScene.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c4acf4f7da..4c18cfa61c 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual.Navigation protected TestOsuGame Game; + protected override bool UseFreshStoragePerRun => true; + [BackgroundDependencyLoader] private void load(GameHost host) { diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 611bc8f30f..f00cefaefd 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -118,6 +118,8 @@ namespace osu.Game.Tests.Visual base.Content.Add(content = new DrawSizePreservingFillContainer()); } + protected virtual bool UseFreshStoragePerRun => false; + public virtual void RecycleLocalStorage() { if (localStorage?.IsValueCreated == true) @@ -133,7 +135,7 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From e43e12cb2dd0504917f27fb2b83efd637885366b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 20:17:59 +0900 Subject: [PATCH 12/39] Pause playback in present tests to avoid track inadvertently changing at menu --- .../Visual/Navigation/TestScenePresentBeatmap.cs | 7 +++++++ osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 27f5b29738..a003b9ae4d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -74,6 +74,13 @@ namespace osu.Game.Tests.Visual.Navigation private void returnToMenu() { + // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). + AddStep("pause audio", () => + { + if (Game.MusicController.IsPlaying) + Game.MusicController.TogglePause(); + }); + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 74037dd3ec..52b577b402 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -110,6 +110,13 @@ namespace osu.Game.Tests.Visual.Navigation private void returnToMenu() { + // if we don't pause, there's a chance the track may change at the main menu out of our control (due to reaching the end of the track). + AddStep("pause audio", () => + { + if (Game.MusicController.IsPlaying) + Game.MusicController.TogglePause(); + }); + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); } From dbfaa4a0df5ae5924d1c640fa200ecea39d318f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 22:50:44 +0900 Subject: [PATCH 13/39] Remove beatmap paths from tests where they would result in exceptions --- osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs | 1 - osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 3 --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 1 - 3 files changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index faea32f90f..55b8902d7b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -54,7 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset = new OsuRuleset().RulesetInfo, OnlineBeatmapID = beatmapId, - Path = "normal.osu", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index a3ea4619cc..3aff390a47 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -839,7 +839,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10, - Path = "normal.osu", Version = "Normal", StarDifficulty = 2, BaseDifficulty = new BeatmapDifficulty @@ -850,7 +849,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10 + 1, - Path = "hard.osu", Version = "Hard", StarDifficulty = 5, BaseDifficulty = new BeatmapDifficulty @@ -861,7 +859,6 @@ namespace osu.Game.Tests.Visual.SongSelect new BeatmapInfo { OnlineBeatmapID = id * 10 + 2, - Path = "insane.osu", Version = "Insane", StarDifficulty = 6, BaseDifficulty = new BeatmapDifficulty diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index f7d66ca5cf..0299b7a084 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -879,7 +879,6 @@ namespace osu.Game.Tests.Visual.SongSelect { Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, - Path = "normal.osu", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, From 3c70b3127c31a9098a294f09749e49d7e9ca6da9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Sep 2020 23:19:31 +0900 Subject: [PATCH 14/39] Fix potential nullref in FilterControl during asynchronous load --- osu.Game/Screens/Select/FilterControl.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c82a3742cc..952a5d1eaa 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -66,24 +66,9 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) { - config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); - showConverted.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); - minimumStars.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); - maximumStars.ValueChanged += _ => updateCriteria(); - - ruleset.BindTo(parentRuleset); - ruleset.BindValueChanged(_ => updateCriteria()); - sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); - groupMode.BindValueChanged(_ => updateCriteria()); - sortMode.BindValueChanged(_ => updateCriteria()); - Children = new Drawable[] { new Box @@ -182,6 +167,21 @@ namespace osu.Game.Screens.Select } }; + config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); + showConverted.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); + minimumStars.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); + maximumStars.ValueChanged += _ => updateCriteria(); + + ruleset.BindTo(parentRuleset); + ruleset.BindValueChanged(_ => updateCriteria()); + + groupMode.BindValueChanged(_ => updateCriteria()); + sortMode.BindValueChanged(_ => updateCriteria()); + collectionDropdown.Current.ValueChanged += val => { if (val.NewValue == null) From c6386ea60505e10b56e2189423fa36d4d0f6a87a Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 15 Sep 2020 21:33:52 -0700 Subject: [PATCH 15/39] Remember leaderboard mods filter selection in song select --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 ++ osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs | 6 ++++++ osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 7 +++++++ 4 files changed, 17 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 71820dea55..207a3f01d3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -24,6 +24,7 @@ namespace osu.Game.Configuration Set(OsuSetting.Skin, 0, -1, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); + Set(OsuSetting.BeatmapDetailModsFilter, false); Set(OsuSetting.ShowConvertedBeatmaps, true); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); @@ -200,6 +201,7 @@ namespace osu.Game.Configuration CursorRotation, MenuParallax, BeatmapDetailTab, + BeatmapDetailModsFilter, Username, ReleaseStream, SavePassword, diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 2e78b1aed2..89ae92ec91 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Select protected Bindable CurrentTab => tabControl.Current; + protected Bindable CurrentModsFilter => tabControl.CurrentModsFilter; + private readonly Container content; protected override Container Content => content; diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 63711e3e50..df8c68a0dd 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -26,6 +26,12 @@ namespace osu.Game.Screens.Select set => tabs.Current = value; } + public Bindable CurrentModsFilter + { + get => modsCheckbox.Current; + set => modsCheckbox.Current = value; + } + public Action OnFilter; // passed the selected tab and if mods is checked public IReadOnlyList TabItems diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index d719502a4f..c87a4bbc54 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Select private Bindable selectedTab; + private Bindable selectedModsFilter; + public PlayBeatmapDetailArea() { Add(Leaderboard = new BeatmapLeaderboard { RelativeSizeAxes = Axes.Both }); @@ -38,8 +40,13 @@ namespace osu.Game.Screens.Select private void load(OsuConfigManager config) { selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); + selectedModsFilter = config.GetBindable(OsuSetting.BeatmapDetailModsFilter); + selectedTab.BindValueChanged(tab => CurrentTab.Value = getTabItemFromTabType(tab.NewValue), true); CurrentTab.BindValueChanged(tab => selectedTab.Value = getTabTypeFromTabItem(tab.NewValue)); + + selectedModsFilter.BindValueChanged(checkbox => CurrentModsFilter.Value = checkbox.NewValue, true); + CurrentModsFilter.BindValueChanged(checkbox => selectedModsFilter.Value = checkbox.NewValue); } public override void Refresh() From ff5b29230261ac3ef26b1e1e375652b29357ddfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Sep 2020 19:36:36 +0900 Subject: [PATCH 16/39] Fix global bindings being lost when running tests under headless contexts --- osu.Game/Tests/Visual/OsuTestScene.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f00cefaefd..b59a1db403 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -69,12 +69,26 @@ namespace osu.Game.Tests.Visual /// protected virtual bool UseOnlineAPI => false; + /// + /// When running headless, there is an opportunity to use the host storage rather than creating a second isolated one. + /// This is because the host is recycled per TestScene execution in headless at an nunit level. + /// + private Storage isolatedHostStorage; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + if (!UseFreshStoragePerRun) + isolatedHostStorage = (parent.Get() as HeadlessGameHost)?.Storage; + contextFactory = new Lazy(() => { var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); + + // only reset the database if not using the host storage. + // if we reset the host storage, it will delete global key bindings. + if (isolatedHostStorage == null) + factory.ResetDatabase(); + using (var usage = factory.Get()) usage.Migrate(); return factory; @@ -135,12 +149,9 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => isolatedHostStorage ?? new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } - [Resolved] - private GameHost host { get; set; } - [Resolved] protected AudioManager Audio { get; private set; } From 9063c60b9cb79ee5fb6db07f736a8510d8371861 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 16 Sep 2020 12:00:27 -0700 Subject: [PATCH 17/39] Fix profile section tab control not absorbing input from behind --- osu.Game/Overlays/UserProfileOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 2b316c0e34..d52ad84592 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; @@ -176,6 +177,10 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Highlight1; } + protected override bool OnClick(ClickEvent e) => true; + + protected override bool OnHover(HoverEvent e) => true; + private class ProfileSectionTabItem : OverlayTabItem { public ProfileSectionTabItem(ProfileSection value) From 3529a1bfeacf3c765731871d54db2b7f01911fb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 16 Sep 2020 19:36:36 +0900 Subject: [PATCH 18/39] Fix global bindings being lost when running tests under headless contexts --- osu.Game/Tests/Visual/OsuTestScene.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f00cefaefd..b59a1db403 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -69,12 +69,26 @@ namespace osu.Game.Tests.Visual /// protected virtual bool UseOnlineAPI => false; + /// + /// When running headless, there is an opportunity to use the host storage rather than creating a second isolated one. + /// This is because the host is recycled per TestScene execution in headless at an nunit level. + /// + private Storage isolatedHostStorage; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { + if (!UseFreshStoragePerRun) + isolatedHostStorage = (parent.Get() as HeadlessGameHost)?.Storage; + contextFactory = new Lazy(() => { var factory = new DatabaseContextFactory(LocalStorage); - factory.ResetDatabase(); + + // only reset the database if not using the host storage. + // if we reset the host storage, it will delete global key bindings. + if (isolatedHostStorage == null) + factory.ResetDatabase(); + using (var usage = factory.Get()) usage.Migrate(); return factory; @@ -135,12 +149,9 @@ namespace osu.Game.Tests.Visual } localStorage = - new Lazy(() => !UseFreshStoragePerRun && host is HeadlessGameHost ? host.Storage : new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); + new Lazy(() => isolatedHostStorage ?? new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } - [Resolved] - private GameHost host { get; set; } - [Resolved] protected AudioManager Audio { get; private set; } From d2580ebc7023b9730dbf4fe4e047dcd597946803 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 13:01:34 +0900 Subject: [PATCH 19/39] Attempt to fix tests by avoiding clash between import tests names --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 14eff4c5e3..ef5ff0e75d 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestBasicImport() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithSameMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithNoMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Skins.IO [Test] public async Task TestImportTwiceWithDifferentMetadata() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) { try { From 0b289d2e779140930d8fd90c3de13bd3b0dcef8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 13:07:05 +0900 Subject: [PATCH 20/39] Add hostname differentiation to beatmap tests too --- .../Beatmaps/IO/ImportBeatmapTest.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index dd3dba1274..bc6fbed07a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWhenClosed() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDelete() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithDifferentFilename() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -259,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportCorruptThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestRollbackOnFailure() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImport() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -406,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString())) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-{set}")) { try { @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO public async Task TestImportWithDuplicateBeatmapIDs() { // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -496,8 +496,8 @@ namespace osu.Game.Tests.Beatmaps.IO [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] public void TestImportOverIPC() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("host", true)) - using (HeadlessGameHost client = new CleanRunHeadlessGameHost("client", true)) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-host", true)) + using (HeadlessGameHost client = new CleanRunHeadlessGameHost($"{nameof(ImportBeatmapTest)}-client", true)) { try { @@ -526,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWhenFileOpen() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithDuplicateHashes() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -590,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportNestedStructure() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -635,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportWithIgnoredDirectoryInArchive() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -689,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapInfo() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -719,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestUpdateBeatmapFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -761,7 +761,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewEmptyBeatmap() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { @@ -788,7 +788,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestCreateNewBeatmapWithObject() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) { try { From 835c8d74b77298faef44e1b37a45596e9ff93cd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 16:12:18 +0900 Subject: [PATCH 21/39] Wait for two update frames before attempting to migrate storage --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 199e69a19d..17e6b712f3 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -272,8 +272,16 @@ namespace osu.Game.Tests.NonVisual { var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + bool ready = false; + // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid + // database access (GlobalActionContainer is one to do this). + host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); + + waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); + return osu; } From 89a2f20922fea81dc585b9681d43a2f995157c17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Sep 2020 16:12:30 +0900 Subject: [PATCH 22/39] Use new CleanRun host class in import tests --- .../NonVisual/CustomDataDirectoryTest.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 17e6b712f3..211fa4ca42 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestDefaultDirectory))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -45,7 +46,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (var host = new CustomTestHeadlessGameHost(nameof(TestCustomDirectory))) + using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); @@ -71,7 +72,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (var host = new CustomTestHeadlessGameHost(nameof(TestSubDirectoryLookup))) + using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); @@ -104,7 +105,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigration))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -165,7 +166,7 @@ namespace osu.Game.Tests.NonVisual string customPath = prepareCustomPath(); string customPath2 = prepareCustomPath("-2"); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationBetweenTwoTargets))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -194,7 +195,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSameTargetFails))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -215,7 +216,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToNestedTargetFails))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -244,7 +245,7 @@ namespace osu.Game.Tests.NonVisual { string customPath = prepareCustomPath(); - using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget))) + using (var host = new CustomTestHeadlessGameHost()) { try { @@ -315,14 +316,14 @@ namespace osu.Game.Tests.NonVisual return path; } - public class CustomTestHeadlessGameHost : HeadlessGameHost + public class CustomTestHeadlessGameHost : CleanRunHeadlessGameHost { public Storage InitialStorage { get; } - public CustomTestHeadlessGameHost(string name) - : base(name) + public CustomTestHeadlessGameHost([CallerMemberName] string callingMethodName = @"") + : base(callingMethodName: callingMethodName) { - string defaultStorageLocation = getDefaultLocationFor(name); + string defaultStorageLocation = getDefaultLocationFor(callingMethodName); InitialStorage = new DesktopStorage(defaultStorageLocation, this); InitialStorage.DeleteDirectory(string.Empty); From 81f0a06fc41a5d3332303133ac885c4595717d5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 16:30:34 +0900 Subject: [PATCH 23/39] Fix potential endless taiko beatmap conversion --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2a1aa5d1df..e6f6b9faac 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Taiko.Objects; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -87,6 +88,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); + if (Precision.AlmostEquals(0, tickSpacing)) + yield break; + int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) From 73a7b759cb048146ff1afb27e63367c5eb95eb27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:04:44 +0900 Subject: [PATCH 24/39] Add missing obsoletion notice --- osu.Game/Rulesets/Objects/HitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 1d60b266e3..0dfde834ee 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -145,6 +145,7 @@ namespace osu.Game.Rulesets.Objects #pragma warning restore 618 } + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 protected virtual void CreateNestedHitObjects() { } From 009e1b44450309991b4e660fef58b41a4df4ad58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:05:24 +0900 Subject: [PATCH 25/39] Make Spinner use cancellation token --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1658a4e7c2..194aa640f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -48,14 +49,16 @@ namespace osu.Game.Rulesets.Osu.Objects MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); int totalSpins = MaximumBonusSpins + SpinsRequired; for (int i = 0; i < totalSpins; i++) { + cancellationToken.ThrowIfCancellationRequested(); + double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired From c7d24203ceb00022fe10d74be6e81d9434e89d6c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 17:40:05 +0900 Subject: [PATCH 26/39] Make beatmap conversion support cancellation tokens --- .../Beatmaps/CatchBeatmapConverter.cs | 3 ++- .../Beatmaps/ManiaBeatmapConverter.cs | 7 ++++--- .../Beatmaps/OsuBeatmapConverter.cs | 3 ++- .../Beatmaps/TaikoBeatmapConverter.cs | 7 ++++--- .../TestSceneDrawableScrollingRuleset.cs | 3 ++- osu.Game/Beatmaps/BeatmapConverter.cs | 21 +++++++++---------- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 ++- osu.Game/Beatmaps/IBeatmapConverter.cs | 5 ++++- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 9 files changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 145a40f5f5..34964fc4ae 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,6 +5,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Framework.Extensions.IEnumerableExtensions; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); - protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { var positionData = obj as IHasXPosition; var comboData = obj as IHasCombo; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 211905835c..524ea27efa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects; using System; using System.Linq; using System.Collections.Generic; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -68,14 +69,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); - protected override Beatmap ConvertBeatmap(IBeatmap original) + protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate); Random = new FastRandom(seed); - return base.ConvertBeatmap(original); + return base.ConvertBeatmap(original, cancellationToken); } protected override Beatmap CreateBeatmap() @@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return beatmap; } - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { if (original is ManiaHitObject maniaOriginal) { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index fcad356a1c..a2fc4848af 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Objects; using System.Collections.Generic; using osu.Game.Rulesets.Objects.Types; using System.Linq; +using System.Threading; using osu.Game.Rulesets.Osu.UI; using osu.Framework.Extensions.IEnumerableExtensions; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition); - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { var positionData = original as IHasPosition; var comboData = original as IHasCombo; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2a1aa5d1df..91e31aeced 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Taiko.Objects; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -48,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps public override bool CanConvert() => true; - protected override Beatmap ConvertBeatmap(IBeatmap original) + protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { // Rewrite the beatmap info to add the slider velocity multiplier original.BeatmapInfo = original.BeatmapInfo.Clone(); original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER; - Beatmap converted = base.ConvertBeatmap(original); + Beatmap converted = base.ConvertBeatmap(original, cancellationToken); if (original.BeatmapInfo.RulesetID == 3) { @@ -72,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps return converted; } - protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken) { // Old osu! used hit sounding to determine various hit type information IList samples = obj.Samples; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index bd7e894cf8..1a1babb4a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -276,7 +277,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override bool CanConvert() => true; - protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { yield return new TestHitObject { diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 11fee030f8..3083cee07e 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -36,34 +37,31 @@ namespace osu.Game.Beatmaps /// public abstract bool CanConvert(); - /// - /// Converts . - /// - /// The converted Beatmap. - public IBeatmap Convert() + public IBeatmap Convert(CancellationToken cancellationToken = default) { // We always operate on a clone of the original beatmap, to not modify it game-wide - return ConvertBeatmap(Beatmap.Clone()); + return ConvertBeatmap(Beatmap.Clone(), cancellationToken); } /// /// Performs the conversion of a Beatmap using this Beatmap Converter. /// /// The un-converted Beatmap. + /// The cancellation token. /// The converted Beatmap. - protected virtual Beatmap ConvertBeatmap(IBeatmap original) + protected virtual Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { var beatmap = CreateBeatmap(); beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; - beatmap.HitObjects = convertHitObjects(original.HitObjects, original).OrderBy(s => s.StartTime).ToList(); + beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; return beatmap; } - private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap) + private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap, CancellationToken cancellationToken) { var result = new List(hitObjects.Count); @@ -75,7 +73,7 @@ namespace osu.Game.Beatmaps continue; } - var converted = ConvertHitObject(obj, beatmap); + var converted = ConvertHitObject(obj, beatmap, cancellationToken); if (ObjectConverted != null) { @@ -104,7 +102,8 @@ namespace osu.Game.Beatmaps /// /// The hit object to convert. /// The un-converted Beatmap. + /// The cancellation token. /// The converted hit object. - protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap); + protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken); } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index af2a2ac250..fdc839ccff 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -76,7 +77,7 @@ namespace osu.Game.Beatmaps public bool CanConvert() => true; - public IBeatmap Convert() + public IBeatmap Convert(CancellationToken cancellationToken) { foreach (var obj in Beatmap.HitObjects) ObjectConverted?.Invoke(obj, obj.Yield()); diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 173d5494ba..83d0ada1b9 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -30,6 +31,8 @@ namespace osu.Game.Beatmaps /// /// Converts . /// - IBeatmap Convert(); + /// The cancellation token. + /// The converted Beatmap. + IBeatmap Convert(CancellationToken cancellationToken); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d9780233d1..30382c444f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -109,7 +109,7 @@ namespace osu.Game.Beatmaps } // Convert - IBeatmap converted = converter.Convert(); + IBeatmap converted = converter.Convert(cancellationSource.Token); // Apply conversion mods to the result foreach (var mod in mods.OfType()) From e71991a53c068cb86bdc77396d7a6ec40640a9fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 18:37:48 +0900 Subject: [PATCH 27/39] Add default token --- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IBeatmapConverter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index fdc839ccff..c114358771 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps public bool CanConvert() => true; - public IBeatmap Convert(CancellationToken cancellationToken) + public IBeatmap Convert(CancellationToken cancellationToken = default) { foreach (var obj in Beatmap.HitObjects) ObjectConverted?.Invoke(obj, obj.Yield()); diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index 83d0ada1b9..2833af8ca2 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -33,6 +33,6 @@ namespace osu.Game.Beatmaps /// /// The cancellation token. /// The converted Beatmap. - IBeatmap Convert(CancellationToken cancellationToken); + IBeatmap Convert(CancellationToken cancellationToken = default); } } From de5ef8a4715dc1c138c6cd3aba93f02765bdae95 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 17 Sep 2020 21:36:55 +0900 Subject: [PATCH 28/39] Rework to support obsoletion --- osu.Game/Beatmaps/BeatmapConverter.cs | 34 ++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 3083cee07e..cb0b3a8d09 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -27,6 +27,8 @@ namespace osu.Game.Beatmaps public IBeatmap Beatmap { get; } + private CancellationToken cancellationToken; + protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { Beatmap = beatmap; @@ -39,6 +41,8 @@ namespace osu.Game.Beatmaps public IBeatmap Convert(CancellationToken cancellationToken = default) { + this.cancellationToken = cancellationToken; + // We always operate on a clone of the original beatmap, to not modify it game-wide return ConvertBeatmap(Beatmap.Clone(), cancellationToken); } @@ -51,6 +55,19 @@ namespace osu.Game.Beatmaps /// The converted Beatmap. protected virtual Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { +#pragma warning disable 618 + return ConvertBeatmap(original); +#pragma warning restore 618 + } + + /// + /// Performs the conversion of a Beatmap using this Beatmap Converter. + /// + /// The un-converted Beatmap. + /// The converted Beatmap. + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 + protected virtual Beatmap ConvertBeatmap(IBeatmap original) + { var beatmap = CreateBeatmap(); beatmap.BeatmapInfo = original.BeatmapInfo; @@ -104,6 +121,21 @@ namespace osu.Game.Beatmaps /// The un-converted Beatmap. /// The cancellation token. /// The converted hit object. - protected abstract IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken); + protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { +#pragma warning disable 618 + return ConvertHitObject(original, beatmap); +#pragma warning restore 618 + } + + /// + /// Performs the conversion of a hit object. + /// This method is generally executed sequentially for all objects in a beatmap. + /// + /// The hit object to convert. + /// The un-converted Beatmap. + /// The converted hit object. + [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 + protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) => Enumerable.Empty(); } } From 83d23c954712e401758a2591ceff241c2384ae14 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 14:56:08 -0700 Subject: [PATCH 29/39] Use new icon in chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 11 ++++++----- osu.Game/Overlays/OverlayTitle.cs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 25a59e9b25..c53eccf78b 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; namespace osu.Game.Overlays { @@ -78,7 +79,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour colours) + private void load(OsuConfigManager config, OsuColour colours, TextureStore textures) { const float padding = 5; @@ -163,13 +164,13 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - new SpriteIcon + new Sprite { - Icon = FontAwesome.Solid.Comments, + Texture = textures.Get(IconTexture), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Margin = new MarginPadding(10), + Size = new Vector2(OverlayTitle.ICON_SIZE), + Margin = new MarginPadding { Left = 10 }, }, ChannelTabControl = CreateChannelTabControl().With(d => { diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs index 17eeece1f8..c3ea35adfc 100644 --- a/osu.Game/Overlays/OverlayTitle.cs +++ b/osu.Game/Overlays/OverlayTitle.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays { public abstract class OverlayTitle : CompositeDrawable, INamedOverlayComponent { + public const float ICON_SIZE = 30; + private readonly OsuSpriteText titleText; private readonly Container icon; @@ -51,7 +53,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side - Size = new Vector2(30) + Size = new Vector2(ICON_SIZE) }, titleText = new OsuSpriteText { From 2ad7e6ca880ec24bc87d0ee3e8bfa6cba8a478b4 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 19:10:58 -0700 Subject: [PATCH 30/39] Fix hovered channel tabs color when unselected --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index 09dc06b95f..cca4dc33e5 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.Chat.Tabs TweenEdgeEffectTo(deactivateEdgeEffect, TRANSITION_LENGTH); - box.FadeColour(BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); + box.FadeColour(IsHovered ? backgroundHover : BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); Text.Font = Text.Font.With(weight: FontWeight.Medium); From c62e4ef5e5b81954db33625748179186947bf53a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 18 Sep 2020 13:06:41 +0900 Subject: [PATCH 31/39] Allow one hitobject in taiko beatmap converter edge case --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9e82161a61..ed7b8589ba 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -89,9 +89,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); - if (Precision.AlmostEquals(0, tickSpacing)) - yield break; - int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) @@ -109,6 +106,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps }; i = (i + 1) % allSamples.Count; + + if (Precision.AlmostEquals(0, tickSpacing)) + break; } } else From 393ee1c9f5e4719efc9eb35ee97752cd7ae6d4ba Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 17 Sep 2020 23:09:09 -0700 Subject: [PATCH 32/39] Fix hovered osu tab items not showing hover state when deselected --- osu.Game/Graphics/UserInterface/OsuTabControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 61501b0cd8..dbcce9a84a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -123,8 +123,8 @@ namespace osu.Game.Graphics.UserInterface protected void FadeUnhovered() { - Bar.FadeOut(transition_length, Easing.OutQuint); - Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); + Bar.FadeTo(IsHovered ? 1 : 0, transition_length, Easing.OutQuint); + Text.FadeColour(IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) From 3cef93ee27dac06ea85c761bc4c24f19e17637ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Sep 2020 18:05:33 +0900 Subject: [PATCH 33/39] Centralise import test helper methods --- .../Beatmaps/IO/ImportBeatmapTest.cs | 50 ++++++-------- .../Collections/IO/ImportCollectionsTest.cs | 62 ++--------------- osu.Game.Tests/ImportTest.cs | 66 +++++++++++++++++++ .../NonVisual/CustomDataDirectoryTest.cs | 47 +++---------- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 55 ++++------------ osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 56 ++++------------ 6 files changed, 129 insertions(+), 207 deletions(-) create mode 100644 osu.Game.Tests/ImportTest.cs diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index bc6fbed07a..80fbda8e1d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -28,7 +28,7 @@ using FileInfo = System.IO.FileInfo; namespace osu.Game.Tests.Beatmaps.IO { [TestFixture] - public class ImportBeatmapTest + public class ImportBeatmapTest : ImportTest { [Test] public async Task TestImportWhenClosed() @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - await LoadOszIntoOsu(loadOsu(host)); + await LoadOszIntoOsu(LoadOsuIntoHost(host)); } finally { @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); var importedSecondTime = await LoadOszIntoOsu(osu); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -263,7 +263,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -314,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.IO Interlocked.Increment(ref loggedExceptionCount); }; - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure @@ -382,7 +382,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -410,7 +410,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var imported = await LoadOszIntoOsu(osu); @@ -444,7 +444,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var metadata = new BeatmapMetadata { @@ -504,7 +504,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(host.IsPrimaryInstance); Assert.IsFalse(client.IsPrimaryInstance); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -530,7 +530,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) await osu.Dependencies.Get().Import(temp); @@ -552,7 +552,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -594,7 +594,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -639,7 +639,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var temp = TestResources.GetTestBeatmapForImport(); @@ -693,7 +693,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var temp = TestResources.GetTestBeatmapForImport(); @@ -723,7 +723,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var temp = TestResources.GetTestBeatmapForImport(); @@ -765,7 +765,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); @@ -792,7 +792,7 @@ namespace osu.Game.Tests.Beatmaps.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var manager = osu.Dependencies.Get(); var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); @@ -863,14 +863,6 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); } - private OsuGameBase loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - Task.Run(() => host.Run(osu)); - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - return osu; - } - private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { IEnumerable resultSets = null; diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index a79e0d0338..a8ee1bcc2e 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -4,18 +4,15 @@ using System; using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Collections; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Collections.IO { [TestFixture] - public class ImportCollectionsTest + public class ImportCollectionsTest : ImportTest { [Test] public async Task TestImportEmptyDatabase() @@ -24,7 +21,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); await osu.CollectionManager.Import(new MemoryStream()); @@ -44,7 +41,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -70,7 +67,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -101,7 +98,7 @@ namespace osu.Game.Tests.Collections.IO { AppDomain.CurrentDomain.UnhandledException += setException; - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); using (var ms = new MemoryStream()) { @@ -135,7 +132,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db")); @@ -156,7 +153,7 @@ namespace osu.Game.Tests.Collections.IO { try { - var osu = loadOsu(host, true); + var osu = LoadOsuIntoHost(host, true); Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); @@ -172,50 +169,5 @@ namespace osu.Game.Tests.Collections.IO } } } - - private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false) - { - var osu = new TestOsuGameBase(withBeatmap); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - - private class TestOsuGameBase : OsuGameBase - { - public CollectionManager CollectionManager { get; private set; } - - private readonly bool withBeatmap; - - public TestOsuGameBase(bool withBeatmap) - { - this.withBeatmap = withBeatmap; - } - - [BackgroundDependencyLoader] - private void load() - { - // Beatmap must be imported before the collection manager is loaded. - if (withBeatmap) - BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); - - AddInternal(CollectionManager = new CollectionManager(Storage)); - } - } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs new file mode 100644 index 0000000000..ea351e0d45 --- /dev/null +++ b/osu.Game.Tests/ImportTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Collections; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests +{ + public abstract class ImportTest + { + protected virtual TestOsuGameBase LoadOsuIntoHost(GameHost host, bool withBeatmap = false) + { + var osu = new TestOsuGameBase(withBeatmap); + Task.Run(() => host.Run(osu)); + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + bool ready = false; + // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid + // database access (GlobalActionContainer is one to do this). + host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); + + waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + + public class TestOsuGameBase : OsuGameBase + { + public CollectionManager CollectionManager { get; private set; } + + private readonly bool withBeatmap; + + public TestOsuGameBase(bool withBeatmap) + { + this.withBeatmap = withBeatmap; + } + + [BackgroundDependencyLoader] + private void load() + { + // Beatmap must be imported before the collection manager is loaded. + if (withBeatmap) + BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + + AddInternal(CollectionManager = new CollectionManager(Storage)); + } + } + } +} diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 211fa4ca42..b6ab73eceb 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -5,8 +5,6 @@ using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework; using osu.Framework.Allocation; @@ -18,7 +16,7 @@ using osu.Game.IO; namespace osu.Game.Tests.NonVisual { [TestFixture] - public class CustomDataDirectoryTest + public class CustomDataDirectoryTest : ImportTest { [Test] public void TestDefaultDirectory() @@ -29,7 +27,7 @@ namespace osu.Game.Tests.NonVisual { string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory)); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); @@ -53,7 +51,7 @@ namespace osu.Game.Tests.NonVisual try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); // switch to DI'd storage var storage = osu.Dependencies.Get(); @@ -79,7 +77,7 @@ namespace osu.Game.Tests.NonVisual try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); // switch to DI'd storage var storage = osu.Dependencies.Get(); @@ -111,7 +109,7 @@ namespace osu.Game.Tests.NonVisual { string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration)); - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); var storage = osu.Dependencies.Get(); // Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes. @@ -170,7 +168,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); const string database_filename = "client.db"; @@ -199,7 +197,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.Throws(() => osu.Migrate(customPath)); @@ -220,7 +218,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); @@ -249,7 +247,7 @@ namespace osu.Game.Tests.NonVisual { try { - var osu = loadOsu(host); + var osu = LoadOsuIntoHost(host); Assert.DoesNotThrow(() => osu.Migrate(customPath)); @@ -269,33 +267,6 @@ namespace osu.Game.Tests.NonVisual } } - private OsuGameBase loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - Task.Run(() => host.Run(osu)); - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - bool ready = false; - // wait for two update frames to be executed. this ensures that all components have had a change to run LoadComplete and hopefully avoid - // database access (GlobalActionContainer is one to do this). - host.UpdateThread.Scheduler.Add(() => host.UpdateThread.Scheduler.Add(() => ready = true)); - - waitForOrAssert(() => ready, @"osu! failed to start in a reasonable amount of time"); - - return osu; - } - - private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - private static string getDefaultLocationFor(string testTypeName) { string path = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testTypeName); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a4d20714fa..7522aca5dc 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; @@ -17,12 +16,11 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Tests.Resources; using osu.Game.Users; namespace osu.Game.Tests.Scores.IO { - public class ImportScoreTest + public class ImportScoreTest : ImportTest { [Test] public async Task TestBasicImport() @@ -31,7 +29,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -45,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO OnlineScoreID = 12345, }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Rank, imported.Rank); Assert.AreEqual(toImport.TotalScore, imported.TotalScore); @@ -70,14 +68,14 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); @@ -96,7 +94,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -107,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); @@ -126,7 +124,7 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); var toImport = new ScoreInfo { @@ -138,7 +136,7 @@ namespace osu.Game.Tests.Scores.IO } }; - var imported = await loadIntoOsu(osu, toImport); + var imported = await loadScoreIntoOsu(osu, toImport); var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); @@ -146,7 +144,7 @@ namespace osu.Game.Tests.Scores.IO beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); - var secondImport = await loadIntoOsu(osu, imported); + var secondImport = await loadScoreIntoOsu(osu, imported); Assert.That(secondImport, Is.Null); } finally @@ -163,9 +161,9 @@ namespace osu.Game.Tests.Scores.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host, true); - await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await loadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); @@ -179,7 +177,7 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) + private async Task loadScoreIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -192,33 +190,6 @@ namespace osu.Game.Tests.Scores.IO return scoreManager.GetAllUsableScores().FirstOrDefault(); } - private async Task loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } - private class TestArchiveReader : ArchiveReader { public TestArchiveReader() diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index ef5ff0e75d..a5b4b04ef5 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -4,20 +4,17 @@ using System; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.IO.Archives; using osu.Game.Skinning; -using osu.Game.Tests.Resources; using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Skins.IO { - public class ImportSkinTest + public class ImportSkinTest : ImportTest { [Test] public async Task TestBasicImport() @@ -26,9 +23,9 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); Assert.That(imported.Name, Is.EqualTo("test skin")); Assert.That(imported.Creator, Is.EqualTo("skinner")); @@ -47,10 +44,10 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin", "skinner"), "skin2.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(1)); @@ -72,11 +69,11 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); // if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety. - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk(string.Empty, string.Empty), "download.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); @@ -98,10 +95,10 @@ namespace osu.Game.Tests.Skins.IO { try { - var osu = await loadOsu(host); + var osu = LoadOsuIntoHost(host); - var imported = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); - var imported2 = await loadIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2", "skinner"), "skin.osk")); + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("test skin v2.1", "skinner"), "skin2.osk")); Assert.That(imported2.ID, Is.Not.EqualTo(imported.ID)); Assert.That(osu.Dependencies.Get().GetAllUserSkins().Count, Is.EqualTo(2)); @@ -141,37 +138,10 @@ namespace osu.Game.Tests.Skins.IO return stream; } - private async Task loadIntoOsu(OsuGameBase osu, ArchiveReader archive = null) + private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); return await skinManager.Import(archive); } - - private async Task loadOsu(GameHost host) - { - var osu = new OsuGameBase(); - -#pragma warning disable 4014 - Task.Run(() => host.Run(osu)); -#pragma warning restore 4014 - - waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); - - var beatmapFile = TestResources.GetTestBeatmapForImport(); - var beatmapManager = osu.Dependencies.Get(); - await beatmapManager.Import(beatmapFile); - - return osu; - } - - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) - { - Task task = Task.Run(() => - { - while (!result()) Thread.Sleep(200); - }); - - Assert.IsTrue(task.Wait(timeout), failureMessage); - } } } From 1fcf443314f2d75d34d791d4a13c8a2e3af8547f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Sep 2020 19:33:03 +0900 Subject: [PATCH 34/39] Ensure BeatmapProcessor.PostProcess is run before firing HitObjectUpdated events --- osu.Game/Screens/Edit/EditorBeatmap.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3a9bd85b0f..3248c5b8be 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -207,14 +207,15 @@ namespace osu.Game.Screens.Edit beatmapProcessor?.PreProcess(); foreach (var hitObject in pendingUpdates) - { processHitObject(hitObject); - HitObjectUpdated?.Invoke(hitObject); - } - - pendingUpdates.Clear(); beatmapProcessor?.PostProcess(); + + // explicitly needs to be fired after PostProcess + foreach (var hitObject in pendingUpdates) + HitObjectUpdated?.Invoke(hitObject); + + pendingUpdates.Clear(); } } From 847ec8c248e640227d781e06ab4f3188d7c3c3b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 19 Sep 2020 14:52:05 +0900 Subject: [PATCH 35/39] Fix n^2 characteristic in taiko diffcalc --- .../Preprocessing/StaminaCheeseDetector.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs index d07bff4369..3b1a9ad777 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Taiko.Objects; @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing // as that index can be simply subtracted from the current index to get the number of elements in between // without off-by-one errors int indexBeforeLastRepeat = -1; + int lastMarkEnd = 0; for (int i = 0; i < hitObjects.Count; i++) { @@ -87,7 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (repeatedLength < roll_min_repetitions) continue; - markObjectsAsCheese(i, repeatedLength); + markObjectsAsCheese(Math.Max(lastMarkEnd, i - repeatedLength + 1), i); + lastMarkEnd = i; } } @@ -113,6 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing private void findTlTap(int parity, HitType type) { int tlLength = -2; + int lastMarkEnd = 0; for (int i = parity; i < hitObjects.Count; i += 2) { @@ -124,17 +128,18 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing if (tlLength < tl_min_repetitions) continue; - markObjectsAsCheese(i, tlLength); + markObjectsAsCheese(Math.Max(lastMarkEnd, i - tlLength + 1), i); + lastMarkEnd = i; } } /// - /// Marks elements counting backwards from as . + /// Marks all objects from to (inclusive) as . /// - private void markObjectsAsCheese(int end, int count) + private void markObjectsAsCheese(int start, int end) { - for (int i = 0; i < count; ++i) - hitObjects[end - i].StaminaCheese = true; + for (int i = start; i <= end; i++) + hitObjects[i].StaminaCheese = true; } } } From e0cef6686d5b80c53d460d540ab4843c12471d30 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 14:54:14 -0500 Subject: [PATCH 36/39] Change collection deletion notif to be consistent --- osu.Game/Collections/CollectionManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index a50ab5b07a..f96a689faf 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -12,6 +12,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -230,7 +231,7 @@ namespace osu.Game.Collections public void DeleteAll() { Collections.Clear(); - PostNotification?.Invoke(new SimpleNotification { Text = "Deleted all collections!" }); + PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!"}); } private readonly object saveLock = new object(); From c49dcca1ff9b2946efe38548ed5ffe7f4e8356b8 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 14:55:52 -0500 Subject: [PATCH 37/39] spacing oops --- osu.Game/Collections/CollectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index f96a689faf..5f0f52125b 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -231,7 +231,7 @@ namespace osu.Game.Collections public void DeleteAll() { Collections.Clear(); - PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!"}); + PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } private readonly object saveLock = new object(); From d2f498a2689031099233203121e171c192fee810 Mon Sep 17 00:00:00 2001 From: S Stewart Date: Sat, 19 Sep 2020 15:13:52 -0500 Subject: [PATCH 38/39] remove unnec using --- osu.Game/Collections/CollectionManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 5f0f52125b..569ac749a4 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -12,7 +12,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; From 5b697580afcf26ce430d6dc7abc991f7a1769946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 16:38:16 +0900 Subject: [PATCH 39/39] Add mention of ruleset templates to readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3e9ca5121..7c749f3422 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,13 @@ If you are looking to install or test osu! without setting up a development envi If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. -## Developing or debugging +## Developing a custom ruleset + +osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu-templates). + +You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852). + +## Developing osu! Please make sure you have the following prerequisites: