From 6b73f7c7ec5b411c9f30ec7b9c8d94d2a2d12e34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 15:04:09 +0900 Subject: [PATCH 01/40] Split out legacy import path from realm manager --- .../Collections/IO/ImportCollectionsTest.cs | 3 +- osu.Game.Tests/ImportTest.cs | 2 +- .../TestSceneManageCollectionsDialog.cs | 2 +- .../SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 40 +++ osu.Game/Collections/BeatmapCollection.cs | 17 - osu.Game/Collections/CollectionManager.cs | 331 ++---------------- osu.Game/Database/LegacyCollectionImporter.cs | 167 +++++++++ osu.Game/Database/LegacyImportManager.cs | 4 +- osu.Game/OsuGame.cs | 2 +- 10 files changed, 237 insertions(+), 333 deletions(-) create mode 100644 osu.Game/Beatmaps/RealmBeatmapCollection.cs create mode 100644 osu.Game/Database/LegacyCollectionImporter.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 9a8f29647d..685586ff02 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Collections.IO @@ -187,7 +188,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); + await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.CollectionManager).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 32b6dc649c..1f18f92158 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); - AddInternal(CollectionManager = new CollectionManager(Storage)); + AddInternal(CollectionManager = new CollectionManager()); } } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 3f30fa367c..789139f483 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Collections base.Content.AddRange(new Drawable[] { - manager = new CollectionManager(LocalStorage), + manager = new CollectionManager(), Content, dialogOverlay = new DialogOverlay(), }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6807180640..01bf312ddd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect base.Content.AddRange(new Drawable[] { - collectionManager = new CollectionManager(LocalStorage), + collectionManager = new CollectionManager(), Content }); diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs new file mode 100644 index 0000000000..d3261fc39e --- /dev/null +++ b/osu.Game/Beatmaps/RealmBeatmapCollection.cs @@ -0,0 +1,40 @@ +// 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 JetBrains.Annotations; +using osu.Game.Database; +using Realms; + +namespace osu.Game.Beatmaps +{ + public class RealmBeatmapCollection : RealmObject, IHasGuidPrimaryKey + { + [PrimaryKey] + public Guid ID { get; } + + public string Name { get; set; } = string.Empty; + + public List BeatmapMD5Hashes { get; set; } = null!; + + /// + /// The date when this collection was last modified. + /// + public DateTimeOffset LastModified { get; set; } + + public RealmBeatmapCollection(string? name, List? beatmapMD5Hashes) + { + ID = Guid.NewGuid(); + Name = name ?? string.Empty; + BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); + + LastModified = DateTimeOffset.UtcNow; + } + + [UsedImplicitly] + private RealmBeatmapCollection() + { + } + } +} diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 742d757bec..abfd0e6dd0 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -14,11 +14,6 @@ namespace osu.Game.Collections /// public class BeatmapCollection { - /// - /// Invoked whenever any change occurs on this . - /// - public event Action Changed; - /// /// The collection's name. /// @@ -33,17 +28,5 @@ namespace osu.Game.Collections /// The date when this collection was last modified. /// public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; - - public BeatmapCollection() - { - BeatmapHashes.CollectionChanged += (_, _) => onChange(); - Name.ValueChanged += _ => onChange(); - } - - private void onChange() - { - LastModifyDate = DateTimeOffset.Now; - Changed?.Invoke(); - } } } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 796b3c426c..0d4ee5c722 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,346 +4,59 @@ #nullable disable using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.IO; -using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; +using Realms; namespace osu.Game.Collections { /// /// Handles user-defined collections of beatmaps. /// - /// - /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the - /// database backing the game. Going forward writing should be done in a similar way to other model stores. - /// public class CollectionManager : Component, IPostNotifications { - /// - /// Database version in stable-compatible YYYYMMDD format. - /// - private const int database_version = 30000000; - - private const string database_name = "collection.db"; - private const string database_backup_name = "collection.db.bak"; - public readonly BindableList Collections = new BindableList(); - private readonly Storage storage; - - public CollectionManager(Storage storage) - { - this.storage = storage; - } - - [Resolved(canBeNull: true)] - private DatabaseContextFactory efContextFactory { get; set; } = null!; + [Resolved] + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load() { - efContextFactory?.WaitForMigrationCompletion(); - - Collections.CollectionChanged += collectionsChanged; - - if (storage.Exists(database_backup_name)) - { - // If a backup file exists, it means the previous write operation didn't run to completion. - // Always prefer the backup file in such a case as it's the most recent copy that is guaranteed to not be malformed. - // - // The database is saved 100ms after any change, and again when the game is closed, so there shouldn't be a large diff between the two files in the worst case. - if (storage.Exists(database_name)) - storage.Delete(database_name); - File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); - } - - if (storage.Exists(database_name)) - { - List beatmapCollections; - - using (var stream = storage.GetStream(database_name)) - beatmapCollections = readCollections(stream); - - // intentionally fire-and-forget async. - importCollections(beatmapCollections); - } } - private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + protected override void LoadComplete() { - switch (e.Action) + base.LoadComplete(); + + realm.RegisterForNotifications(r => r.All(), collectionsChanged); + } + + private void collectionsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // TODO: hook up with realm changes. + + if (changes == null) { - case NotifyCollectionChangedAction.Add: - foreach (var c in e.NewItems.Cast()) - c.Changed += backgroundSave; - break; - - case NotifyCollectionChangedAction.Remove: - foreach (var c in e.OldItems.Cast()) - c.Changed -= backgroundSave; - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var c in e.OldItems.Cast()) - c.Changed -= backgroundSave; - - foreach (var c in e.NewItems.Cast()) - c.Changed += backgroundSave; - break; + foreach (var collection in sender) + Collections.Add(new BeatmapCollection + { + Name = { Value = collection.Name }, + BeatmapHashes = { Value = collection.BeatmapMD5Hashes }, + }); } - - backgroundSave(); - }); + } public Action PostNotification { protected get; set; } - public Task GetAvailableCount(StableStorage stableStorage) - { - if (!stableStorage.Exists(database_name)) - return Task.FromResult(0); - - return Task.Run(() => - { - using (var stream = stableStorage.GetStream(database_name)) - return readCollections(stream).Count; - }); - } - - /// - /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. - /// - public Task ImportFromStableAsync(StableStorage stableStorage) - { - if (!stableStorage.Exists(database_name)) - { - // This handles situations like when the user does not have a collections.db file - Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - return Task.Run(async () => - { - using (var stream = stableStorage.GetStream(database_name)) - await Import(stream).ConfigureAwait(false); - }); - } - - public async Task Import(Stream stream) - { - var notification = new ProgressNotification - { - State = ProgressNotificationState.Active, - Text = "Collections import is initialising..." - }; - - PostNotification?.Invoke(notification); - - var collections = readCollections(stream, notification); - await importCollections(collections).ConfigureAwait(false); - - notification.CompletionText = $"Imported {collections.Count} collections"; - notification.State = ProgressNotificationState.Completed; - } - - private Task importCollections(List newCollections) - { - var tcs = new TaskCompletionSource(); - - Schedule(() => - { - try - { - foreach (var newCol in newCollections) - { - var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); - if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); - - foreach (string newBeatmap in newCol.BeatmapHashes) - { - if (!existing.BeatmapHashes.Contains(newBeatmap)) - existing.BeatmapHashes.Add(newBeatmap); - } - } - - tcs.SetResult(true); - } - catch (Exception e) - { - Logger.Error(e, "Failed to import collection."); - tcs.SetException(e); - } - }); - - return tcs.Task; - } - - private List readCollections(Stream stream, ProgressNotification notification = null) - { - if (notification != null) - { - notification.Text = "Reading collections..."; - notification.Progress = 0; - } - - var result = new List(); - - try - { - using (var sr = new SerializationReader(stream)) - { - sr.ReadInt32(); // Version - - int collectionCount = sr.ReadInt32(); - result.Capacity = collectionCount; - - for (int i = 0; i < collectionCount; i++) - { - if (notification?.CancellationToken.IsCancellationRequested == true) - return result; - - var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; - int mapCount = sr.ReadInt32(); - - for (int j = 0; j < mapCount; j++) - { - if (notification?.CancellationToken.IsCancellationRequested == true) - return result; - - string checksum = sr.ReadString(); - - collection.BeatmapHashes.Add(checksum); - } - - if (notification != null) - { - notification.Text = $"Imported {i + 1} of {collectionCount} collections"; - notification.Progress = (float)(i + 1) / collectionCount; - } - - result.Add(collection); - } - } - } - catch (Exception e) - { - Logger.Error(e, "Failed to read collection database."); - } - - return result; - } - public void DeleteAll() { Collections.Clear(); PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } - - private readonly object saveLock = new object(); - private int lastSave; - private int saveFailures; - - /// - /// Perform a save with debounce. - /// - private void backgroundSave() - { - int current = Interlocked.Increment(ref lastSave); - Task.Delay(100).ContinueWith(_ => - { - if (current != lastSave) - return; - - if (!save()) - backgroundSave(); - }); - } - - private bool save() - { - lock (saveLock) - { - Interlocked.Increment(ref lastSave); - - // This is NOT thread-safe!! - try - { - string tempPath = Path.GetTempFileName(); - - using (var ms = new MemoryStream()) - { - using (var sw = new SerializationWriter(ms, true)) - { - sw.Write(database_version); - - var collectionsCopy = Collections.ToArray(); - sw.Write(collectionsCopy.Length); - - foreach (var c in collectionsCopy) - { - sw.Write(c.Name.Value); - - string[] beatmapsCopy = c.BeatmapHashes.ToArray(); - - sw.Write(beatmapsCopy.Length); - - foreach (string b in beatmapsCopy) - sw.Write(b); - } - } - - using (var fs = File.OpenWrite(tempPath)) - ms.WriteTo(fs); - - string databasePath = storage.GetFullPath(database_name); - string databaseBackupPath = storage.GetFullPath(database_backup_name); - - // Back up the existing database, clearing any existing backup. - if (File.Exists(databaseBackupPath)) - File.Delete(databaseBackupPath); - if (File.Exists(databasePath)) - File.Move(databasePath, databaseBackupPath); - - // Move the new database in-place of the existing one. - File.Move(tempPath, databasePath); - - // If everything succeeded up to this point, remove the backup file. - if (File.Exists(databaseBackupPath)) - File.Delete(databaseBackupPath); - } - - if (saveFailures < 10) - saveFailures = 0; - return true; - } - catch (Exception e) - { - // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). - // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. - if (++saveFailures == 10) - Logger.Error(e, "Failed to save collection database!"); - } - - return false; - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - save(); - } } } diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs new file mode 100644 index 0000000000..8168419e80 --- /dev/null +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -0,0 +1,167 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Logging; +using osu.Game.Collections; +using osu.Game.IO; +using osu.Game.IO.Legacy; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Database +{ + public class LegacyCollectionImporter + { + private readonly CollectionManager collections; + + public LegacyCollectionImporter(CollectionManager collections) + { + this.collections = collections; + } + + public Action PostNotification { protected get; set; } + + private const string database_name = "collection.db"; + + public Task GetAvailableCount(StableStorage stableStorage) + { + if (!stableStorage.Exists(database_name)) + return Task.FromResult(0); + + return Task.Run(() => + { + using (var stream = stableStorage.GetStream(database_name)) + return readCollections(stream).Count; + }); + } + + /// + /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. + /// + public Task ImportFromStableAsync(StableStorage stableStorage) + { + if (!stableStorage.Exists(database_name)) + { + // This handles situations like when the user does not have a collections.db file + Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(async () => + { + using (var stream = stableStorage.GetStream(database_name)) + await Import(stream).ConfigureAwait(false); + }); + } + + public async Task Import(Stream stream) + { + var notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Collections import is initialising..." + }; + + PostNotification?.Invoke(notification); + + var importedCollections = readCollections(stream, notification); + await importCollections(importedCollections).ConfigureAwait(false); + + notification.CompletionText = $"Imported {importedCollections.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } + + private Task importCollections(List newCollections) + { + var tcs = new TaskCompletionSource(); + + // Schedule(() => + // { + try + { + foreach (var newCol in newCollections) + { + var existing = collections.Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); + if (existing == null) + collections.Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + + foreach (string newBeatmap in newCol.BeatmapHashes) + { + if (!existing.BeatmapHashes.Contains(newBeatmap)) + existing.BeatmapHashes.Add(newBeatmap); + } + } + + tcs.SetResult(true); + } + catch (Exception e) + { + Logger.Error(e, "Failed to import collection."); + tcs.SetException(e); + } + // }); + + return tcs.Task; + } + + private List readCollections(Stream stream, ProgressNotification notification = null) + { + if (notification != null) + { + notification.Text = "Reading collections..."; + notification.Progress = 0; + } + + var result = new List(); + + try + { + using (var sr = new SerializationReader(stream)) + { + sr.ReadInt32(); // Version + + int collectionCount = sr.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) + { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; + int mapCount = sr.ReadInt32(); + + for (int j = 0; j < mapCount; j++) + { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + + string checksum = sr.ReadString(); + + collection.BeatmapHashes.Add(checksum); + } + + if (notification != null) + { + notification.Text = $"Imported {i + 1} of {collectionCount} collections"; + notification.Progress = (float)(i + 1) / collectionCount; + } + + result.Add(collection); + } + } + } + catch (Exception e) + { + Logger.Error(e, "Failed to read collection database."); + } + + return result; + } + } +} diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index f40e0d33c2..05bd5ceb54 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); case StableContent.Collections: - return await collections.GetAvailableCount(stableStorage); + return await new LegacyCollectionImporter(collections).GetAvailableCount(stableStorage); case StableContent.Scores: return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); @@ -109,7 +109,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(collections).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1ee53e2848..8d8864a46a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -858,7 +858,7 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(new CollectionManager(Storage) + loadComponentSingleFile(new CollectionManager { PostNotification = n => Notifications.Post(n), }, Add, true); From 2d4655f61e011d71f504202cadc6601bd6ac580f Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 02:25:40 -0400 Subject: [PATCH 02/40] Add Toast Notification to Copy URL --- .../UserInterface/ExternalLinkButton.cs | 25 ++++++++++++++++++- osu.Game/Localisation/ToastStrings.cs | 5 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index acabeca66e..810cd6ef58 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -12,7 +12,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Localisation; using osu.Framework.Platform; +using osu.Game.Overlays; +using osu.Game.Overlays.OSD; using osuTK; using osuTK.Graphics; @@ -27,6 +30,9 @@ namespace osu.Game.Graphics.UserInterface [Resolved] private GameHost host { get; set; } + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + private readonly SpriteIcon linkIcon; public ExternalLinkButton(string link = null) @@ -44,6 +50,23 @@ namespace osu.Game.Graphics.UserInterface }; } + private class CopyUrlToast : Toast + { + public CopyUrlToast(LocalisableString value) + : base(UserInterfaceStrings.GeneralHeader, value, "") + { + } + } + + private void copyUrl() + { + if (Link != null) + { + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.CopiedUrl)); + } + } + public MenuItem[] ContextMenuItems { get @@ -53,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => host.GetClipboard()?.SetText(Link))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); } return items.ToArray(); diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 9ceee807e6..d6771fcd96 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Localisation /// public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); + /// + /// "Copied URL" + /// + public static LocalisableString CopiedUrl => new TranslatableString(getKey(@"copied_url"), @"Copied URL"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 9c543fef481b0afda90d60288a111dd10a544067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 15:59:36 +0900 Subject: [PATCH 03/40] Remove `CollectionManager` --- .../Collections/IO/ImportCollectionsTest.cs | 83 ++++++++---- osu.Game.Tests/ImportTest.cs | 6 +- .../TestSceneManageCollectionsDialog.cs | 120 +++++++++++------- .../SongSelect/TestSceneFilterControl.cs | 52 ++++---- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 4 +- .../Collections/CollectionFilterDropdown.cs | 6 +- osu.Game/Collections/CollectionManager.cs | 62 --------- .../Collections/CollectionToggleMenuItem.cs | 10 +- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 17 ++- .../Collections/DrawableCollectionListItem.cs | 47 ++++--- .../Collections/ManageCollectionsDialog.cs | 5 - osu.Game/Database/LegacyCollectionImporter.cs | 54 ++++---- osu.Game/Database/LegacyImportManager.cs | 11 +- osu.Game/OsuGame.cs | 5 - .../Maintenance/CollectionsSettings.cs | 23 +++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 10 +- .../Carousel/DrawableCarouselBeatmap.cs | 18 ++- .../Carousel/DrawableCarouselBeatmapSet.cs | 30 ++--- 19 files changed, 276 insertions(+), 292 deletions(-) delete mode 100644 osu.Game/Collections/CollectionManager.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 685586ff02..32503cdb12 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -5,12 +5,14 @@ using System; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Tests.Resources; @@ -30,7 +32,11 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, new MemoryStream()); - Assert.That(osu.CollectionManager.Collections.Count, Is.Zero); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.Zero); + }); } finally { @@ -50,18 +56,22 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(2)); - // Even with no beatmaps imported, collections are tracking the hashes and will continue to. - // In the future this whole mechanism will be replaced with having the collections in realm, - // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps - // and have them associate with collections if/when they become available. + // Even with no beatmaps imported, collections are tracking the hashes and will continue to. + // In the future this whole mechanism will be replaced with having the collections in realm, + // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps + // and have them associate with collections if/when they become available. - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12)); + Assert.That(collections[1].Name, Is.EqualTo("Second")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12)); + }); } finally { @@ -81,13 +91,18 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1)); + Assert.That(collections.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1)); + + Assert.That(collections[1].Name, Is.EqualTo("Second")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12)); + }); } finally { @@ -124,7 +139,11 @@ namespace osu.Game.Tests.Collections.IO } Assert.That(exceptionThrown, Is.False); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(0)); + }); } finally { @@ -149,12 +168,18 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - // Move first beatmap from second collection into the first. - osu.CollectionManager.Collections[0].BeatmapHashes.Add(osu.CollectionManager.Collections[1].BeatmapHashes[0]); - osu.CollectionManager.Collections[1].BeatmapHashes.RemoveAt(0); + // ReSharper disable once MethodHasAsyncOverload + osu.Realm.Write(realm => + { + var collections = realm.All().ToList(); - // Rename the second collecction. - osu.CollectionManager.Collections[1].Name.Value = "Another"; + // Move first beatmap from second collection into the first. + collections[0].BeatmapMD5Hashes.Add(collections[1].BeatmapMD5Hashes[0]); + collections[1].BeatmapMD5Hashes.RemoveAt(0); + + // Rename the second collecction. + collections[1].Name = "Another"; + }); } finally { @@ -169,13 +194,17 @@ namespace osu.Game.Tests.Collections.IO { var osu = LoadOsuIntoHost(host, true); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(2)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(11)); + Assert.That(collections[1].Name, Is.EqualTo("Another")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(11)); + }); } finally { @@ -188,7 +217,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.CollectionManager).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); + await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.Realm).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 1f18f92158..23ca31ee42 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -10,7 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; -using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -47,7 +47,7 @@ namespace osu.Game.Tests public class TestOsuGameBase : OsuGameBase { - public CollectionManager CollectionManager { get; private set; } + public RealmAccess Realm => Dependencies.Get(); private readonly bool withBeatmap; @@ -62,8 +62,6 @@ namespace osu.Game.Tests // Beatmap must be imported before the collection manager is loaded. if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); - - AddInternal(CollectionManager = new CollectionManager()); } } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 789139f483..21d2b0328b 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -27,13 +25,10 @@ namespace osu.Game.Tests.Visual.Collections { protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private DialogOverlay dialogOverlay; - private CollectionManager manager; - - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - - private ManageCollectionsDialog dialog; + private DialogOverlay dialogOverlay = null!; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private ManageCollectionsDialog dialog = null!; [BackgroundDependencyLoader] private void load(GameHost host) @@ -46,19 +41,17 @@ namespace osu.Game.Tests.Visual.Collections base.Content.AddRange(new Drawable[] { - manager = new CollectionManager(), Content, dialogOverlay = new DialogOverlay(), }); - Dependencies.Cache(manager); Dependencies.CacheAs(dialogOverlay); } [SetUp] public void SetUp() => Schedule(() => { - manager.Collections.Clear(); + Realm.Write(r => r.RemoveAll()); Child = dialog = new ManageCollectionsDialog(); }); @@ -78,17 +71,17 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestLastItemIsPlaceholder() { - AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } [Test] public void TestAddCollectionExternal() { - AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "First collection")))); assertCollectionCount(1); assertCollectionName(0, "First collection"); - AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } })); + AddStep("add another collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "Second collection")))); assertCollectionCount(2); assertCollectionName(1, "Second collection"); } @@ -108,7 +101,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestAddCollectionViaPlaceholder() { - DrawableCollectionListItem placeholderItem = null; + DrawableCollectionListItem placeholderItem = null!; AddStep("focus placeholder", () => { @@ -117,23 +110,31 @@ namespace osu.Game.Tests.Visual.Collections }); // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a"); + AddStep("change collection name", () => placeholderItem.Model.Name = "a"); assertCollectionCount(1); - AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model)); + AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); - AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } [Test] public void TestRemoveCollectionExternal() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + RealmBeatmapCollection first = null!; - AddStep("remove first collection", () => manager.Collections.RemoveAt(0)); + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); + + AddStep("change first collection name", () => Realm.Write(r => r.Remove(first))); assertCollectionCount(1); assertCollectionName(0, "2"); } @@ -151,21 +152,27 @@ namespace osu.Game.Tests.Visual.Collections Width = 0.4f, }); }); - AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] + AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "1") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); } [Test] public void TestRemoveCollectionViaButton() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + AddStep("add two collections", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); assertCollectionCount(2); @@ -198,10 +205,13 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionNotRemovedWhenDialogCancelled() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + AddStep("add collection", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); assertCollectionCount(1); @@ -224,13 +234,21 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedExternal() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + RealmBeatmapCollection first = null!; - AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First"); + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); + + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); assertCollectionName(0, "First"); } @@ -238,16 +256,24 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedOnTextChange() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + RealmBeatmapCollection first = null!; + + AddStep("add two collections", () => { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); assertCollectionCount(2); AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); - AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); + AddUntilStep("collection has new name", () => first.Name == "First"); } private void assertCollectionCount(int count) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 01bf312ddd..e4d69334a3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -28,12 +26,9 @@ namespace osu.Game.Tests.Visual.SongSelect { protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private CollectionManager collectionManager; - - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - - private FilterControl control; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private FilterControl control = null!; [BackgroundDependencyLoader] private void load(GameHost host) @@ -46,17 +41,14 @@ namespace osu.Game.Tests.Visual.SongSelect base.Content.AddRange(new Drawable[] { - collectionManager = new CollectionManager(), Content }); - - Dependencies.Cache(collectionManager); } [SetUp] public void SetUp() => Schedule(() => { - collectionManager.Collections.Clear(); + Realm.Write(r => r.RemoveAll()); Child = control = new FilterControl { @@ -77,8 +69,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionAddedToDropdown() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); assertCollectionDropdownContains("1"); assertCollectionDropdownContains("2"); } @@ -86,9 +78,11 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); - AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0)); + var first = new RealmBeatmapCollection(name: "1"); + + AddStep("add collection", () => Realm.Write(r => r.Add(first))); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); assertCollectionDropdownContains("1", false); assertCollectionDropdownContains("2"); @@ -97,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRenamed() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -106,7 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); - AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); + AddStep("change name", () => Realm.Write(_ => getFirstCollection().Name = "First")); assertCollectionDropdownContains("First"); assertCollectionHeaderDisplays("First"); @@ -124,7 +118,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionFilterHasAddButton() { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -134,7 +128,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -150,13 +144,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); - AddStep("add beatmap to collection", () => collectionManager.Collections[0].BeatmapHashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); - AddStep("remove beatmap from collection", () => collectionManager.Collections[0].BeatmapHashes.Clear()); + AddStep("remove beatmap from collection", () => getFirstCollection().BeatmapMD5Hashes.Clear()); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -167,15 +161,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); - AddAssert("collection contains beatmap", () => collectionManager.Collections[0].BeatmapHashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddAssert("collection contains beatmap", () => getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); - AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].BeatmapHashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddAssert("collection does not contain beatmap", () => !getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -184,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -202,6 +196,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); } + private RealmBeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs index d3261fc39e..22ba9d5789 100644 --- a/osu.Game/Beatmaps/RealmBeatmapCollection.cs +++ b/osu.Game/Beatmaps/RealmBeatmapCollection.cs @@ -16,14 +16,14 @@ namespace osu.Game.Beatmaps public string Name { get; set; } = string.Empty; - public List BeatmapMD5Hashes { get; set; } = null!; + public IList BeatmapMD5Hashes { get; } = null!; /// /// The date when this collection was last modified. /// public DateTimeOffset LastModified { get; set; } - public RealmBeatmapCollection(string? name, List? beatmapMD5Hashes) + public RealmBeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) { ID = Guid.NewGuid(); Name = name ?? string.Empty; diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index d099eb6e1b..ed2c0c7cfb 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -46,9 +46,6 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - public CollectionFilterDropdown() { ItemSource = filters; @@ -59,8 +56,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - if (collectionManager != null) - collections.BindTo(collectionManager.Collections); + // TODO: bind to realm data // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs deleted file mode 100644 index 0d4ee5c722..0000000000 --- a/osu.Game/Collections/CollectionManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Overlays.Notifications; -using Realms; - -namespace osu.Game.Collections -{ - /// - /// Handles user-defined collections of beatmaps. - /// - public class CollectionManager : Component, IPostNotifications - { - public readonly BindableList Collections = new BindableList(); - - [Resolved] - private RealmAccess realm { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - realm.RegisterForNotifications(r => r.All(), collectionsChanged); - } - - private void collectionsChanged(IRealmCollection sender, ChangeSet changes, Exception error) - { - // TODO: hook up with realm changes. - - if (changes == null) - { - foreach (var collection in sender) - Collections.Add(new BeatmapCollection - { - Name = { Value = collection.Name }, - BeatmapHashes = { Value = collection.BeatmapMD5Hashes }, - }); - } - } - - public Action PostNotification { protected get; set; } - - public void DeleteAll() - { - Collections.Clear(); - PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); - } - } -} diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index f2b10305b8..632249913d 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -8,16 +8,16 @@ namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name.Value, MenuItemType.Standard, state => + public CollectionToggleMenuItem(RealmBeatmapCollection collection, IBeatmapInfo beatmap) + : base(collection.Name, MenuItemType.Standard, state => { if (state) - collection.BeatmapHashes.Add(beatmap.MD5Hash); + collection.BeatmapMD5Hashes.Add(beatmap.MD5Hash); else - collection.BeatmapHashes.Remove(beatmap.MD5Hash); + collection.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); }) { - State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash); + State.Value = collection.BeatmapMD5Hashes.Contains(beatmap.MD5Hash); } } } diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 1da2870913..33c2174623 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -6,16 +6,17 @@ using System; using Humanizer; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(RealmBeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = $"{collection.Name.Value} ({"beatmap".ToQuantity(collection.BeatmapHashes.Count)})"; + BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 4fe5733c2f..63f04641f4 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -7,28 +7,31 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Collections { /// - /// Visualises a list of s. + /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { private Scroll scroll; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + // TODO: source from realm + + protected override OsuRearrangeableListItem CreateOsuDrawable(RealmBeatmapCollection item) { - if (item == scroll.PlaceholderItem.Model) + if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); return new DrawableCollectionListItem(item, true); @@ -95,7 +98,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new RealmBeatmapCollection(), false)); return previous; } @@ -104,7 +107,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 4596fc0e52..a29b2ef81c 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -12,6 +12,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -22,15 +24,15 @@ using osuTK.Graphics; namespace osu.Game.Collections { /// - /// Visualises a inside a . + /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside the . + /// Whether the currently exists inside realm. /// public IBindable IsCreated => isCreated; @@ -39,9 +41,9 @@ namespace osu.Game.Collections /// /// Creates a new . /// - /// The . - /// Whether currently exists inside the . - public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) + /// The . + /// Whether currently exists inside realm. + public DrawableCollectionListItem(RealmBeatmapCollection item, bool isCreated) : base(item) { this.isCreated.Value = isCreated; @@ -61,24 +63,18 @@ namespace osu.Game.Collections { public readonly Bindable IsCreated = new Bindable(); - private readonly IBindable collectionName; - private readonly BeatmapCollection collection; - - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } + private readonly RealmBeatmapCollection collection; private Container textBoxPaddingContainer; private ItemTextBox textBox; - public ItemContent(BeatmapCollection collection) + public ItemContent(RealmBeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.X; Height = item_height; Masking = true; - - collectionName = collection.Name.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -111,14 +107,17 @@ namespace osu.Game.Collections }; } + [Resolved] + private RealmAccess realm { get; set; } + protected override void LoadComplete() { base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current = collection.Name; + textBox.Current.Value = collection.Name; + textBox.Current.BindValueChanged(_ => createNewCollection(), true); - collectionName.BindValueChanged(_ => createNewCollection(), true); IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); } @@ -127,11 +126,11 @@ namespace osu.Game.Collections if (IsCreated.Value) return; - if (string.IsNullOrEmpty(collectionName.Value)) + if (string.IsNullOrEmpty(textBox.Current.Value)) return; // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - collectionManager?.Collections.Add(collection); + realm.Write(r => r.Add(collection)); textBox.PlaceholderText = string.Empty; // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. @@ -162,15 +161,15 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } + [Resolved] + private RealmAccess realmAccess { get; set; } - private readonly BeatmapCollection collection; + private readonly RealmBeatmapCollection collection; private Drawable fadeContainer; private Drawable background; - public DeleteButton(BeatmapCollection collection) + public DeleteButton(RealmBeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; @@ -227,7 +226,7 @@ namespace osu.Game.Collections { background.FlashColour(Color4.White, 150); - if (collection.BeatmapHashes.Count == 0) + if (collection.BeatmapMD5Hashes.Count == 0) deleteCollection(); else dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); @@ -235,7 +234,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => collectionManager?.Collections.Remove(collection); + private void deleteCollection() => realmAccess.Write(r => r.Remove(collection)); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index a9d699bc9f..721e0d632e 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -26,9 +25,6 @@ namespace osu.Game.Collections private AudioFilter lowPassFilter; - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - public ManageCollectionsDialog() { Anchor = Anchor.Centre; @@ -107,7 +103,6 @@ namespace osu.Game.Collections new DrawableCollectionList { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = collectionManager?.Collections ?? new BindableList() } } } } diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index 8168419e80..aa98c491b1 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; -using osu.Game.Collections; +using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -17,17 +16,17 @@ namespace osu.Game.Database { public class LegacyCollectionImporter { - private readonly CollectionManager collections; + public Action? PostNotification { protected get; set; } - public LegacyCollectionImporter(CollectionManager collections) - { - this.collections = collections; - } - - public Action PostNotification { protected get; set; } + private readonly RealmAccess realm; private const string database_name = "collection.db"; + public LegacyCollectionImporter(RealmAccess realm) + { + this.realm = realm; + } + public Task GetAvailableCount(StableStorage stableStorage) { if (!stableStorage.Exists(database_name)) @@ -76,26 +75,30 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } - private Task importCollections(List newCollections) + private Task importCollections(List newCollections) { var tcs = new TaskCompletionSource(); - // Schedule(() => - // { try { - foreach (var newCol in newCollections) + realm.Write(r => { - var existing = collections.Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); - if (existing == null) - collections.Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); - - foreach (string newBeatmap in newCol.BeatmapHashes) + foreach (var collection in newCollections) { - if (!existing.BeatmapHashes.Contains(newBeatmap)) - existing.BeatmapHashes.Add(newBeatmap); + var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); + + if (existing != null) + { + foreach (string newBeatmap in existing.BeatmapMD5Hashes) + { + if (!existing.BeatmapMD5Hashes.Contains(newBeatmap)) + existing.BeatmapMD5Hashes.Add(newBeatmap); + } + } + else + r.Add(collection); } - } + }); tcs.SetResult(true); } @@ -104,12 +107,11 @@ namespace osu.Game.Database Logger.Error(e, "Failed to import collection."); tcs.SetException(e); } - // }); return tcs.Task; } - private List readCollections(Stream stream, ProgressNotification notification = null) + private List readCollections(Stream stream, ProgressNotification? notification = null) { if (notification != null) { @@ -117,7 +119,7 @@ namespace osu.Game.Database notification.Progress = 0; } - var result = new List(); + var result = new List(); try { @@ -133,7 +135,7 @@ namespace osu.Game.Database if (notification?.CancellationToken.IsCancellationRequested == true) return result; - var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; + var collection = new RealmBeatmapCollection(sr.ReadString()); int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) @@ -143,7 +145,7 @@ namespace osu.Game.Database string checksum = sr.ReadString(); - collection.BeatmapHashes.Add(checksum); + collection.BeatmapMD5Hashes.Add(checksum); } if (notification != null) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 05bd5ceb54..baa117fe07 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.IO; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Maintenance; @@ -36,15 +35,15 @@ namespace osu.Game.Database [Resolved] private ScoreManager scores { get; set; } - [Resolved] - private CollectionManager collections { get; set; } - [Resolved(canBeNull: true)] private OsuGame game { get; set; } [Resolved] private IDialogOverlay dialogOverlay { get; set; } + [Resolved] + private RealmAccess realmAccess { get; set; } + [Resolved(canBeNull: true)] private DesktopGameHost desktopGameHost { get; set; } @@ -72,7 +71,7 @@ namespace osu.Game.Database return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); case StableContent.Collections: - return await new LegacyCollectionImporter(collections).GetAvailableCount(stableStorage); + return await new LegacyCollectionImporter(realmAccess).GetAvailableCount(stableStorage); case StableContent.Scores: return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); @@ -109,7 +108,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(collections).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d8864a46a..78cc4d7f70 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -858,11 +858,6 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(new CollectionManager - { - PostNotification = n => Notifications.Post(n), - }, Add, true); - loadComponentSingleFile(legacyImportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 5367f644ca..0b17ab9c6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -3,9 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; -using osu.Game.Collections; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Localisation; +using osu.Game.Overlays.Notifications; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -15,11 +16,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton importCollectionsButton = null!; - [BackgroundDependencyLoader] - private void load(CollectionManager? collectionManager, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) - { - if (collectionManager == null) return; + [Resolved] + private RealmAccess realm { get; set; } = null!; + [Resolved] + private INotificationOverlay notificationOverlay { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) + { if (legacyImportManager?.SupportsImportFromStable == true) { Add(importCollectionsButton = new SettingsButton @@ -38,9 +43,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = MaintenanceSettingsStrings.DeleteAllCollections, Action = () => { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections)); } }); } + + private void deleteAllCollections() + { + realm.Write(r => r.RemoveAll()); + notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index f38077a9a7..b17c4934cd 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -94,6 +94,9 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + [Resolved] + private RealmAccess realm { get; set; } + [Resolved] private RulesetStore rulesets { get; set; } @@ -112,9 +115,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } @@ -495,11 +495,11 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (collectionManager != null && beatmap != null) + if (beatmap != null) { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 50e30c68d5..bfc93b34e2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -22,6 +22,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -63,12 +64,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -237,14 +238,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - if (collectionManager != null) - { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8c266c8dff..3726d955bd 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -32,12 +33,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } + public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; [CanBeNull] @@ -223,14 +224,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineID > 0 && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); - if (collectionManager != null) - { - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); @@ -241,13 +239,13 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(BeatmapCollection collection) + private MenuItem createCollectionMenuItem(RealmBeatmapCollection collection) { Debug.Assert(beatmapSet != null); TernaryState state; - int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapHashes.Contains(b.MD5Hash)); + int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapMD5Hashes.Contains(b.MD5Hash)); if (countExisting == beatmapSet.Beatmaps.Count) state = TernaryState.True; @@ -256,21 +254,21 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { switch (s) { case TernaryState.True: - if (collection.BeatmapHashes.Contains(b.MD5Hash)) + if (collection.BeatmapMD5Hashes.Contains(b.MD5Hash)) continue; - collection.BeatmapHashes.Add(b.MD5Hash); + collection.BeatmapMD5Hashes.Add(b.MD5Hash); break; case TernaryState.False: - collection.BeatmapHashes.Remove(b.MD5Hash); + collection.BeatmapMD5Hashes.Remove(b.MD5Hash); break; } } From 41393616d80440b78000abd8db504e8b73f19ef1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 16:46:23 +0900 Subject: [PATCH 04/40] Replace `BeatmapCollection` with `RealmBeatmapCollection` --- .../Collections/IO/ImportCollectionsTest.cs | 14 ++-- .../TestSceneManageCollectionsDialog.cs | 38 +++++----- .../SongSelect/TestSceneFilterControl.cs | 26 +++---- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 40 ----------- osu.Game/Collections/BeatmapCollection.cs | 32 +++++++-- .../Collections/CollectionFilterDropdown.cs | 70 +++++++++++-------- .../Collections/CollectionFilterMenuItem.cs | 17 ++--- .../Collections/CollectionToggleMenuItem.cs | 2 +- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 21 +++--- .../Collections/DrawableCollectionListItem.cs | 45 ++++++------ osu.Game/Database/LegacyCollectionImporter.cs | 12 ++-- osu.Game/Overlays/Music/Playlist.cs | 2 +- .../Maintenance/CollectionsSettings.cs | 4 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 4 +- 18 files changed, 158 insertions(+), 180 deletions(-) delete mode 100644 osu.Game/Beatmaps/RealmBeatmapCollection.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 32503cdb12..604b87dc4c 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -12,7 +12,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Tests.Resources; @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.Zero); }); } @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); // Even with no beatmaps imported, collections are tracking the hashes and will continue to. @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Collections.IO Assert.That(exceptionThrown, Is.False); osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(0)); }); } @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Collections.IO // ReSharper disable once MethodHasAsyncOverload osu.Realm.Write(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); // Move first beatmap from second collection into the first. collections[0].BeatmapMD5Hashes.Add(collections[1].BeatmapMD5Hashes[0]); @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); Assert.That(collections[0].Name, Is.EqualTo("First")); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 21d2b0328b..8de38eb4e7 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Collections [SetUp] public void SetUp() => Schedule(() => { - Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); Child = dialog = new ManageCollectionsDialog(); }); @@ -77,11 +77,11 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestAddCollectionExternal() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "First collection")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "First collection")))); assertCollectionCount(1); assertCollectionName(0, "First collection"); - AddStep("add another collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "Second collection")))); + AddStep("add another collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "Second collection")))); assertCollectionCount(2); assertCollectionName(1, "Second collection"); } @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Collections }); // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.Name = "a"); + AddStep("change collection name", () => placeholderItem.Model.PerformWrite(c => c.Name = "a")); assertCollectionCount(1); AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestRemoveCollectionExternal() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -128,13 +128,13 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); - AddStep("change first collection name", () => Realm.Write(r => r.Remove(first))); + AddStep("remove first collection", () => Realm.Write(r => r.Remove(first))); assertCollectionCount(1); assertCollectionName(0, "2"); } @@ -154,8 +154,8 @@ namespace osu.Game.Tests.Visual.Collections }); AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "1") + new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "1") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -167,8 +167,8 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add two collections", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2") + new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add collection", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1") + new BeatmapCollection(name: "1") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedExternal() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -242,8 +242,8 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedOnTextChange() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -264,8 +264,8 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index e4d69334a3..2a4613c37b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUp] public void SetUp() => Schedule(() => { - Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); Child = control = new FilterControl { @@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionAddedToDropdown() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); assertCollectionDropdownContains("1"); assertCollectionDropdownContains("2"); } @@ -78,10 +78,10 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - var first = new RealmBeatmapCollection(name: "1"); + var first = new BeatmapCollection(name: "1"); AddStep("add collection", () => Realm.Write(r => r.Add(first))); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); assertCollectionDropdownContains("1", false); @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRenamed() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionFilterHasAddButton() { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); @@ -161,7 +161,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -193,10 +193,10 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name == "1"); } - private RealmBeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); + private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs deleted file mode 100644 index 22ba9d5789..0000000000 --- a/osu.Game/Beatmaps/RealmBeatmapCollection.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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 JetBrains.Annotations; -using osu.Game.Database; -using Realms; - -namespace osu.Game.Beatmaps -{ - public class RealmBeatmapCollection : RealmObject, IHasGuidPrimaryKey - { - [PrimaryKey] - public Guid ID { get; } - - public string Name { get; set; } = string.Empty; - - public IList BeatmapMD5Hashes { get; } = null!; - - /// - /// The date when this collection was last modified. - /// - public DateTimeOffset LastModified { get; set; } - - public RealmBeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) - { - ID = Guid.NewGuid(); - Name = name ?? string.Empty; - BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); - - LastModified = DateTimeOffset.UtcNow; - } - - [UsedImplicitly] - private RealmBeatmapCollection() - { - } - } -} diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index abfd0e6dd0..2ffe17d9e6 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -1,32 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using osu.Framework.Bindables; +using System.Collections.Generic; +using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Database; +using Realms; namespace osu.Game.Collections { /// /// A collection of beatmaps grouped by a name. /// - public class BeatmapCollection + public class BeatmapCollection : RealmObject, IHasGuidPrimaryKey { + [PrimaryKey] + public Guid ID { get; set; } + /// /// The collection's name. /// - public readonly Bindable Name = new Bindable(); + public string Name { get; set; } = string.Empty; /// /// The es of beatmaps contained by the collection. /// - public readonly BindableList BeatmapHashes = new BindableList(); + public IList BeatmapMD5Hashes { get; } = null!; /// /// The date when this collection was last modified. /// - public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; + public DateTimeOffset LastModified { get; set; } + + public BeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) + { + ID = Guid.NewGuid(); + Name = name ?? string.Empty; + BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); + + LastModified = DateTimeOffset.UtcNow; + } + + [UsedImplicitly] + private BeatmapCollection() + { + } } } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ed2c0c7cfb..1315cebc8b 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; @@ -43,8 +42,8 @@ namespace osu.Game.Collections private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); - [Resolved(CanBeNull = true)] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } public CollectionFilterDropdown() { @@ -81,7 +80,7 @@ namespace osu.Game.Collections if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; } /// @@ -92,11 +91,12 @@ namespace osu.Game.Collections // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; - if (filter.OldValue?.Collection != null) - beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapHashes); - - if (filter.NewValue?.Collection != null) - beatmaps.BindTo(filter.NewValue.Collection.BeatmapHashes); + // TODO: binding with realm + // if (filter.OldValue?.Collection != null) + // beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapMD5Hashes); + // + // if (filter.NewValue?.Collection != null) + // beatmaps.BindTo(filter.NewValue.Collection.BeatmapMD5Hashes); beatmaps.CollectionChanged += filterBeatmapsChanged; @@ -187,26 +187,24 @@ namespace osu.Game.Collections protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { - [NotNull] protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; [Resolved] - private IBindable beatmap { get; set; } + private IBindable beatmap { get; set; } = null!; - [CanBeNull] - private readonly BindableList collectionBeatmaps; - - [NotNull] private readonly Bindable collectionName; - private IconButton addOrRemoveButton; - private Content content; + private IconButton addOrRemoveButton = null!; + private Content content = null!; private bool beatmapInCollection; + private IDisposable? realmSubscription; + + private BeatmapCollection? collection => Item.Collection; + public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionBeatmaps = Item.Collection?.BeatmapHashes.GetBoundCopy(); collectionName = Item.CollectionName.GetBoundCopy(); } @@ -223,14 +221,17 @@ namespace osu.Game.Collections }); } + [Resolved] + private RealmAccess realm { get; set; } = null!; + protected override void LoadComplete() { base.LoadComplete(); - if (collectionBeatmaps != null) + if (Item.Collection != null) { - collectionBeatmaps.CollectionChanged += (_, _) => collectionChanged(); - beatmap.BindValueChanged(_ => collectionChanged(), true); + realmSubscription = realm.SubscribeToPropertyChanged(r => r.Find(Item.Collection.ID), c => c.BeatmapMD5Hashes, _ => hashesChanged()); + beatmap.BindValueChanged(_ => hashesChanged(), true); } // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge @@ -252,11 +253,11 @@ namespace osu.Game.Collections base.OnHoverLost(e); } - private void collectionChanged() + private void hashesChanged() { - Debug.Assert(collectionBeatmaps != null); + Debug.Assert(collection != null); - beatmapInCollection = collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo.MD5Hash); + beatmapInCollection = collection.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -273,7 +274,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (collectionBeatmaps == null) + if (collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -281,13 +282,22 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(collectionBeatmaps != null); + Debug.Assert(collection != null); - if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) - collectionBeatmaps.Add(beatmap.Value.BeatmapInfo.MD5Hash); + realm.Write(r => + { + if (!collection.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) + collection.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); + }); } protected override Drawable CreateContent() => content = (Content)base.CreateContent(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } } } } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 031f05c0b4..4c132ba7b7 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using JetBrains.Annotations; using osu.Framework.Bindables; namespace osu.Game.Collections @@ -18,26 +15,26 @@ namespace osu.Game.Collections /// The collection to filter beatmaps from. /// May be null to not filter by collection (include all beatmaps). /// - [CanBeNull] - public readonly BeatmapCollection Collection; + public readonly BeatmapCollection? Collection; /// /// The name of the collection. /// - [NotNull] public readonly Bindable CollectionName; /// /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem([CanBeNull] BeatmapCollection collection) + public CollectionFilterMenuItem(BeatmapCollection? collection) { Collection = collection; - CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); + CollectionName = new Bindable(collection?.Name ?? "All beatmaps"); } - public bool Equals(CollectionFilterMenuItem other) + // TODO: track name changes i guess? + + public bool Equals(CollectionFilterMenuItem? other) { if (other == null) return false; @@ -45,7 +42,7 @@ namespace osu.Game.Collections // collections may have the same name, so compare first on reference equality. // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager. if (Collection != null) - return Collection == other.Collection; + return Collection.ID == other.Collection?.ID; // fallback to name-based comparison. // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 632249913d..8c0e3c587b 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -8,7 +8,7 @@ namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(RealmBeatmapCollection collection, IBeatmapInfo beatmap) + public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) : base(collection.Name, MenuItemType.Standard, state => { if (state) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 33c2174623..7594978870 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -1,19 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using Humanizer; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(RealmBeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 63f04641f4..f376d18224 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -1,35 +1,33 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Collections { /// - /// Visualises a list of s. + /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { - private Scroll scroll; + private Scroll scroll = null!; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; // TODO: source from realm - protected override OsuRearrangeableListItem CreateOsuDrawable(RealmBeatmapCollection item) + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) { if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); @@ -49,7 +47,7 @@ namespace osu.Game.Collections /// /// The currently-displayed placeholder item. /// - public DrawableCollectionListItem PlaceholderItem { get; private set; } + public DrawableCollectionListItem PlaceholderItem { get; private set; } = null!; protected override Container Content => content; private readonly Container content; @@ -79,6 +77,7 @@ namespace osu.Game.Collections }); ReplacePlaceholder(); + Debug.Assert(PlaceholderItem != null); } protected override void Update() @@ -98,7 +97,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new RealmBeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); return previous; } @@ -107,7 +106,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a29b2ef81c..6093e69deb 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -24,15 +21,15 @@ using osuTK.Graphics; namespace osu.Game.Collections { /// - /// Visualises a inside a . + /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside realm. + /// Whether the currently exists inside realm. /// public IBindable IsCreated => isCreated; @@ -41,9 +38,9 @@ namespace osu.Game.Collections /// /// Creates a new . /// - /// The . + /// The . /// Whether currently exists inside realm. - public DrawableCollectionListItem(RealmBeatmapCollection item, bool isCreated) + public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { this.isCreated.Value = isCreated; @@ -63,12 +60,15 @@ namespace osu.Game.Collections { public readonly Bindable IsCreated = new Bindable(); - private readonly RealmBeatmapCollection collection; + private readonly BeatmapCollection collection; - private Container textBoxPaddingContainer; - private ItemTextBox textBox; + private Container textBoxPaddingContainer = null!; + private ItemTextBox textBox = null!; - public ItemContent(RealmBeatmapCollection collection) + [Resolved] + private RealmAccess realm { get; set; } = null!; + + public ItemContent(BeatmapCollection collection) { this.collection = collection; @@ -107,9 +107,6 @@ namespace osu.Game.Collections }; } - [Resolved] - private RealmAccess realm { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -156,20 +153,20 @@ namespace osu.Game.Collections { public readonly IBindable IsCreated = new Bindable(); - public Func IsTextBoxHovered; - - [Resolved(CanBeNull = true)] - private IDialogOverlay dialogOverlay { get; set; } + public Func IsTextBoxHovered = null!; [Resolved] - private RealmAccess realmAccess { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } - private readonly RealmBeatmapCollection collection; + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; - private Drawable fadeContainer; - private Drawable background; + private readonly BeatmapCollection collection; - public DeleteButton(RealmBeatmapCollection collection) + private Drawable fadeContainer = null!; + private Drawable background = null!; + + public DeleteButton(BeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index aa98c491b1..bd32b8c446 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -7,7 +7,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -75,7 +75,7 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } - private Task importCollections(List newCollections) + private Task importCollections(List newCollections) { var tcs = new TaskCompletionSource(); @@ -85,7 +85,7 @@ namespace osu.Game.Database { foreach (var collection in newCollections) { - var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); + var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); if (existing != null) { @@ -111,7 +111,7 @@ namespace osu.Game.Database return tcs.Task; } - private List readCollections(Stream stream, ProgressNotification? notification = null) + private List readCollections(Stream stream, ProgressNotification? notification = null) { if (notification != null) { @@ -119,7 +119,7 @@ namespace osu.Game.Database notification.Progress = 0; } - var result = new List(); + var result = new List(); try { @@ -135,7 +135,7 @@ namespace osu.Game.Database if (notification?.CancellationToken.IsCancellationRequested == true) return result; - var collection = new RealmBeatmapCollection(sr.ReadString()); + var collection = new BeatmapCollection(sr.ReadString()); int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 954c4de493..19b1b3f84e 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Music else { item.InSelectedCollection = item.Model.Value.Beatmaps.Select(b => b.MD5Hash) - .Any(criteria.Collection.BeatmapHashes.Contains); + .Any(criteria.Collection.BeatmapMD5Hashes.Contains); } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 0b17ab9c6c..498859db46 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -3,7 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Overlays.Notifications; @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { - realm.Write(r => r.RemoveAll()); + realm.Write(r => r.RemoveAll()); notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index b17c4934cd..0826c4144b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 81734745c4..9267434a66 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.BeatmapHashes.Contains(BeatmapInfo.MD5Hash) ?? true; + match &= criteria.Collection?.BeatmapMD5Hashes.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index bfc93b34e2..a1c433b440 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 3726d955bd..75c9daf1b1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineID > 0 && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); - var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(RealmBeatmapCollection collection) + private MenuItem createCollectionMenuItem(BeatmapCollection collection) { Debug.Assert(beatmapSet != null); From 438067a18b4748501c49a57db0201212eb22829c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 17:17:43 +0900 Subject: [PATCH 05/40] Convert realm data propagation to more correctly use `Live` wip --- .../SongSelect/TestSceneFilterControl.cs | 8 +++--- .../Collections/CollectionFilterDropdown.cs | 13 ++++----- .../Collections/CollectionFilterMenuItem.cs | 7 ++--- .../Collections/CollectionToggleMenuItem.cs | 18 ++++++++----- osu.Game/Overlays/Music/FilterCriteria.cs | 3 ++- osu.Game/Overlays/Music/Playlist.cs | 6 +++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 27 +++++++++++-------- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 4 +-- 12 files changed, 54 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 2a4613c37b..aaaa0aec95 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -147,10 +147,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); - AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddStep("add beatmap to collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash))); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); - AddStep("remove beatmap from collection", () => getFirstCollection().BeatmapMD5Hashes.Clear()); + AddStep("remove beatmap from collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Clear())); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any()); } private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 1315cebc8b..c04f617eb9 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections set => current.Current = value; } - private readonly IBindableList collections = new BindableList(); + private readonly IBindableList> collections = new BindableList>(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); @@ -114,6 +114,7 @@ namespace osu.Game.Collections /// private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) { + // TODO: fuck this shit right off // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. Current.TriggerChange(); @@ -200,7 +201,7 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; - private BeatmapCollection? collection => Item.Collection; + private Live? collection => Item.Collection; public CollectionDropdownMenuItem(MenuItem item) : base(item) @@ -257,7 +258,7 @@ namespace osu.Game.Collections { Debug.Assert(collection != null); - beatmapInCollection = collection.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash); + beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -284,10 +285,10 @@ namespace osu.Game.Collections { Debug.Assert(collection != null); - realm.Write(r => + collection.PerformWrite(c => { - if (!collection.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) - collection.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); + if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) + c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); }); } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 4c132ba7b7..fd9e333915 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Game.Database; namespace osu.Game.Collections { @@ -15,7 +16,7 @@ namespace osu.Game.Collections /// The collection to filter beatmaps from. /// May be null to not filter by collection (include all beatmaps). /// - public readonly BeatmapCollection? Collection; + public readonly Live? Collection; /// /// The name of the collection. @@ -26,10 +27,10 @@ namespace osu.Game.Collections /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem(BeatmapCollection? collection) + public CollectionFilterMenuItem(Live? collection) { Collection = collection; - CollectionName = new Bindable(collection?.Name ?? "All beatmaps"); + CollectionName = new Bindable(collection?.PerformRead(c => c.Name) ?? "All beatmaps"); } // TODO: track name changes i guess? diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 8c0e3c587b..5ad06a72c0 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -2,22 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name, MenuItemType.Standard, state => + public CollectionToggleMenuItem(Live collection, IBeatmapInfo beatmap) + : base(collection.PerformRead(c => c.Name), MenuItemType.Standard, state => { - if (state) - collection.BeatmapMD5Hashes.Add(beatmap.MD5Hash); - else - collection.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); + collection.PerformWrite(c => + { + if (state) + c.BeatmapMD5Hashes.Add(beatmap.MD5Hash); + else + c.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); + }); }) { - State.Value = collection.BeatmapMD5Hashes.Contains(beatmap.MD5Hash); + State.Value = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.MD5Hash)); } } } diff --git a/osu.Game/Overlays/Music/FilterCriteria.cs b/osu.Game/Overlays/Music/FilterCriteria.cs index f435c4e6e4..ad491be845 100644 --- a/osu.Game/Overlays/Music/FilterCriteria.cs +++ b/osu.Game/Overlays/Music/FilterCriteria.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using osu.Game.Collections; +using osu.Game.Database; namespace osu.Game.Overlays.Music { @@ -19,6 +20,6 @@ namespace osu.Game.Overlays.Music /// The collection to filter beatmaps from. /// [CanBeNull] - public BeatmapCollection Collection; + public Live Collection; } } diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 19b1b3f84e..2bb0ff1085 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -31,14 +31,16 @@ namespace osu.Game.Overlays.Music { var items = (SearchContainer>>)ListContainer; + string[] currentCollectionHashes = criteria.Collection?.PerformRead(c => c.BeatmapMD5Hashes.ToArray()); + foreach (var item in items.OfType()) { - if (criteria.Collection == null) + if (currentCollectionHashes == null) item.InSelectedCollection = true; else { item.InSelectedCollection = item.Model.Value.Beatmaps.Select(b => b.MD5Hash) - .Any(criteria.Collection.BeatmapMD5Hashes.Contains); + .Any(currentCollectionHashes.Contains); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 0826c4144b..492bb8ada5 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9267434a66..5b17b412ae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.BeatmapMD5Hashes.Contains(BeatmapInfo.MD5Hash) ?? true; + match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a1c433b440..c3cb04680b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 75c9daf1b1..040f954bba 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -254,24 +254,29 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; + var liveCollection = collection.ToLive(realm); + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => { - foreach (var b in beatmapSet.Beatmaps) + liveCollection.PerformWrite(c => { - switch (s) + foreach (var b in beatmapSet.Beatmaps) { - case TernaryState.True: - if (collection.BeatmapMD5Hashes.Contains(b.MD5Hash)) - continue; + switch (s) + { + case TernaryState.True: + if (c.BeatmapMD5Hashes.Contains(b.MD5Hash)) + continue; - collection.BeatmapMD5Hashes.Add(b.MD5Hash); - break; + c.BeatmapMD5Hashes.Add(b.MD5Hash); + break; - case TernaryState.False: - collection.BeatmapMD5Hashes.Remove(b.MD5Hash); - break; + case TernaryState.False: + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + break; + } } - } + }); }) { State = { Value = state } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index d39862b65f..5f5344a338 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value?.Collection + CollectionBeatmapMD5Hashes = collectionDropdown?.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) }; if (!minimumStars.IsDefault) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c7e6e8496a..320bfb1b45 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -68,10 +68,10 @@ namespace osu.Game.Screens.Select } /// - /// The collection to filter beatmaps from. + /// Hashes from the to filter to. /// [CanBeNull] - public BeatmapCollection Collection; + public IEnumerable CollectionBeatmapMD5Hashes { get; set; } [CanBeNull] public IRulesetFilterCriteria RulesetCriteria { get; set; } From 804bb33aedbc4f44a8aed6fc7c90d7528106b1df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 18:24:37 +0900 Subject: [PATCH 06/40] Hook up remaining data flows --- .../Collections/CollectionFilterDropdown.cs | 147 +++++------------- .../Collections/CollectionFilterMenuItem.cs | 21 +-- 2 files changed, 54 insertions(+), 114 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index c04f617eb9..ec409df4d6 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -17,6 +16,7 @@ using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; +using Realms; namespace osu.Game.Collections { @@ -38,13 +38,15 @@ namespace osu.Game.Collections set => current.Current = value; } - private readonly IBindableList> collections = new BindableList>(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved] private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } = null!; + public CollectionFilterDropdown() { ItemSource = filters; @@ -55,51 +57,49 @@ namespace osu.Game.Collections { base.LoadComplete(); - // TODO: bind to realm data + realm.RegisterForNotifications(r => r.All(), collectionsChanged); // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. // An extra bindable is enough to subvert this behaviour. base.Current = Current; - collections.BindCollectionChanged((_, _) => collectionsChanged(), true); - Current.BindValueChanged(filterChanged, true); + Current.BindValueChanged(currentChanged, true); } /// /// Occurs when a collection has been added or removed. /// - private void collectionsChanged() + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); filters.Add(new AllBeatmapsCollectionFilterMenuItem()); - filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c))); + filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; + + // Trigger a re-filter if the current item was in the changeset. + if (selectedItem != null && changes != null) + { + foreach (int index in changes.ModifiedIndices) + { + if (collections[index].ID == selectedItem.ID) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); + } + } + } } - /// - /// Occurs when the selection has changed. - /// - private void filterChanged(ValueChangedEvent filter) + private void currentChanged(ValueChangedEvent filter) { - // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. - beatmaps.CollectionChanged -= filterBeatmapsChanged; - - // TODO: binding with realm - // if (filter.OldValue?.Collection != null) - // beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapMD5Hashes); - // - // if (filter.NewValue?.Collection != null) - // beatmaps.BindTo(filter.NewValue.Collection.BeatmapMD5Hashes); - - beatmaps.CollectionChanged += filterBeatmapsChanged; - // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. if (filter.NewValue is ManageCollectionsFilterMenuItem) @@ -109,18 +109,7 @@ namespace osu.Game.Collections } } - /// - /// Occurs when the beatmaps contained by a have changed. - /// - private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - // TODO: fuck this shit right off - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } - - protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; + protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => { @@ -136,13 +125,6 @@ namespace osu.Game.Collections public class CollectionDropdownHeader : OsuDropdownHeader { public readonly Bindable SelectedItem = new Bindable(); - private readonly Bindable collectionName = new Bindable(); - - protected override LocalisableString Label - { - get => base.Label; - set { } // See updateText(). - } public CollectionDropdownHeader() { @@ -150,26 +132,6 @@ namespace osu.Game.Collections Icon.Size = new Vector2(16); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SelectedItem.BindValueChanged(_ => updateBindable(), true); - } - - private void updateBindable() - { - collectionName.UnbindAll(); - - if (SelectedItem.Value != null) - collectionName.BindTo(SelectedItem.Value.CollectionName); - - collectionName.BindValueChanged(_ => updateText(), true); - } - - // Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here. - private void updateText() => base.Label = collectionName.Value; } protected class CollectionDropdownMenu : OsuDropdownMenu @@ -190,23 +152,16 @@ namespace osu.Game.Collections { protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; - [Resolved] - private IBindable beatmap { get; set; } = null!; - - private readonly Bindable collectionName; - private IconButton addOrRemoveButton = null!; - private Content content = null!; + private bool beatmapInCollection; - private IDisposable? realmSubscription; - - private Live? collection => Item.Collection; + [Resolved] + private IBindable beatmap { get; set; } = null!; public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionName = Item.CollectionName.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -222,22 +177,25 @@ namespace osu.Game.Collections }); } - [Resolved] - private RealmAccess realm { get; set; } = null!; - protected override void LoadComplete() { base.LoadComplete(); if (Item.Collection != null) { - realmSubscription = realm.SubscribeToPropertyChanged(r => r.Find(Item.Collection.ID), c => c.BeatmapMD5Hashes, _ => hashesChanged()); - beatmap.BindValueChanged(_ => hashesChanged(), true); - } + beatmap.BindValueChanged(_ => + { + Debug.Assert(Item.Collection != null); - // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge - // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. - collectionName.BindValueChanged(name => content.Text = name.NewValue, true); + beatmapInCollection = Item.Collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); + + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; + addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; + + updateButtonVisibility(); + }, true); + } updateButtonVisibility(); } @@ -254,19 +212,6 @@ namespace osu.Game.Collections base.OnHoverLost(e); } - private void hashesChanged() - { - Debug.Assert(collection != null); - - beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); - - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; - addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; - addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; - - updateButtonVisibility(); - } - protected override void OnSelectChange() { base.OnSelectChange(); @@ -275,7 +220,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (collection == null) + if (Item.Collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -283,22 +228,16 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(collection != null); + Debug.Assert(Item.Collection != null); - collection.PerformWrite(c => + Item.Collection.PerformWrite(c => { if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); }); } - protected override Drawable CreateContent() => content = (Content)base.CreateContent(); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - realmSubscription?.Dispose(); - } + protected override Drawable CreateContent() => (Content)base.CreateContent(); } } } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index fd9e333915..2ac5784f09 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Bindables; using osu.Game.Database; namespace osu.Game.Collections @@ -21,19 +20,22 @@ namespace osu.Game.Collections /// /// The name of the collection. /// - public readonly Bindable CollectionName; + public string CollectionName { get; } /// /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem(Live? collection) + public CollectionFilterMenuItem(Live collection) + : this(collection.PerformRead(c => c.Name)) { Collection = collection; - CollectionName = new Bindable(collection?.PerformRead(c => c.Name) ?? "All beatmaps"); } - // TODO: track name changes i guess? + protected CollectionFilterMenuItem(string name) + { + CollectionName = name; + } public bool Equals(CollectionFilterMenuItem? other) { @@ -47,16 +49,16 @@ namespace osu.Game.Collections // fallback to name-based comparison. // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). - return CollectionName.Value == other.CollectionName.Value; + return CollectionName == other.CollectionName; } - public override int GetHashCode() => CollectionName.Value.GetHashCode(); + public override int GetHashCode() => CollectionName.GetHashCode(); } public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem { public AllBeatmapsCollectionFilterMenuItem() - : base(null) + : base("All beatmaps") { } } @@ -64,9 +66,8 @@ namespace osu.Game.Collections public class ManageCollectionsFilterMenuItem : CollectionFilterMenuItem { public ManageCollectionsFilterMenuItem() - : base(null) + : base("Manage collections...") { - CollectionName.Value = "Manage collections..."; } } } From 4120c20968b157fe1d8a2ab2bfba823986e8d7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:43:15 +0800 Subject: [PATCH 07/40] Remove the nullable disable annotation in the Mania ruleset. --- osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs | 2 -- 36 files changed, 72 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs index 0bae893810..410386c9d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 8f8b7cb091..050b302bd8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index f9c51bf6a2..d444c9b634 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 8d54923e7b..f0db742eac 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs index 603d096ed7..073dda9de8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs index 20dfc14f09..614ef76a3b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index b166f3ebc3..bec0a6a1d3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs index d053e64315..0817f8f9fc 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index 66627e6ed3..a302f95966 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index f25a77278b..c78bf72979 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index ff236d33bf..4093aeb2a7 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index ddbd7c5d6a..f80c9e1f7c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 58005df561..8ef5bfd94c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index 87c81c2866..014954dd60 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index 380edca515..d9de06a811 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 698555ddc4..e3ac624a6e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 8c4fd0a8fc..a65938184c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index f202b859b1..4cbdaee323 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 9ce4fb6a48..948979505c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey1 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs index f378ce3435..684370fc3d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey10 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 5812df80f5..de91902ca8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey2 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 4116ed5ceb..8575a96bde 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey3 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 9879fec686..54ea3afa07 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey4 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index 646386b0d8..e9a9bba5bd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey5 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 56af9ed589..b9606d1cb5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey6 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index a0a7116ed7..b80d794085 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey7 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index fc8ecdb9ea..3462d634a4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey8 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index c495a6c82f..83c505c048 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey9 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 6e56981fc8..9c3744ea98 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs index 076f634968..33ebcf303a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index 9ae664e1f6..4cc712060c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs index 487f32dc26..e8988be548 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index 2789a2a06e..2e22e23dbd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 5da29e5a1d..3c24e91d54 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 22347d21b8..dfb02408d2 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs index 17759d718e..ecc343ecaa 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods From 9f79e80d8b23159db0cbfe5a1541d1536498124b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:31:04 +0800 Subject: [PATCH 08/40] Remove nullable disable annotation in the Mania test case. --- .../Mods/TestSceneManiaModConstantSpeed.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs index 538c8b13d1..60363aaeef 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index 4222be0359..7970d5b594 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs index 4c97f65b07..f2cc254e38 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 9612543483..2e3b21aed7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; From d766052be4872917bb75b6db821ea72be8e8c70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:59:14 +0800 Subject: [PATCH 09/40] Remove nullable disable annotation in the Taiko ruleset. --- osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs | 2 -- 19 files changed, 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 6c01bae027..4b74b4991e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs index d1c192f7fa..fee0cb2744 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 233179c9ec..d18b88761d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs index 873aa7f992..84aa5e6bba 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 564d023c5a..99a064d35f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Game.Beatmaps; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index b19c2eaccf..89581c57bd 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index a37e1c6f5c..ad6fdf59e2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fe02a6caf9..43230bc035 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs index ddfc2d1174..68d6305fbf 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 7780936e7d..ba41175461 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index fe3e5ca11c..538d0a9d7a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs index 874e15406d..0f1e0b2885 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index e02a16f62f..7cb14635ff 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs index 57ecf0224f..bf1006f1aa 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs index c65dba243b..b107b14a03 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index f58f59aaf2..307a37bf2e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Utils; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index a3a644ab99..7be70d9ac3 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs index 037e376ad2..7a0f6c7cd1 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs index c9cba59760..3cb337c41d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Game.Beatmaps; From 860e9d42ff2236841d309facc8c29284f9c6fc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:00:48 +0800 Subject: [PATCH 10/40] Mark the property as nullable and add some assert check. --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 5 ++++- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 5 +++-- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index d18b88761d..f7fdd447d6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.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.Diagnostics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield { - private DrawableTaikoRuleset drawableTaikoRuleset; + private DrawableTaikoRuleset? drawableTaikoRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Mods public void Update(Playfield playfield) { + Debug.Assert(drawableTaikoRuleset != null); + // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. const float scroll_rate = 10; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 43230bc035..3820e55e75 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Layout; using osu.Game.Configuration; @@ -36,9 +37,9 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield.AsNonNull()); - private TaikoPlayfield playfield; + private TaikoPlayfield? playfield; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 538d0a9d7a..dab2279351 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.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.Diagnostics; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private const float fade_out_duration = 0.375f; - private DrawableTaikoRuleset drawableRuleset; + private DrawableTaikoRuleset? drawableRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { + Debug.Assert(drawableRuleset != null); + switch (hitObject) { case DrawableDrumRollTick: From 3510c1656672962aa026b7a0bcbd4123408941a5 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:38:02 +0800 Subject: [PATCH 11/40] Remove nullable disable annotation in the Taiko test case. --- osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs | 2 -- osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs | 2 -- osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs index ca2f8102b7..3090facf8c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests.Mods diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index 0b28bfee2e..7abbb9d186 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Taiko.Mods; diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index 917462c128..a83cc16413 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Mods; From ac39d3a1424470e41ea107ed7246088d51d20cbc Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 21:52:28 -0400 Subject: [PATCH 12/40] "Copied URL" -> "URL copied" --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Localisation/ToastStrings.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 810cd6ef58..87d67939bf 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -76,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); } return items.ToArray(); diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index d6771fcd96..da798a3937 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -45,9 +45,9 @@ namespace osu.Game.Localisation public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); /// - /// "Copied URL" + /// "URL copied" /// - public static LocalisableString CopiedUrl => new TranslatableString(getKey(@"copied_url"), @"Copied URL"); + public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); private static string getKey(string key) => $@"{prefix}:{key}"; } From f097064eea6ab93cc6ca8702317ab783e0bc5c70 Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 21:52:38 -0400 Subject: [PATCH 13/40] Adjust to reviews --- .../UserInterface/ExternalLinkButton.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 87d67939bf..762b4bd90d 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -50,21 +50,10 @@ namespace osu.Game.Graphics.UserInterface }; } - private class CopyUrlToast : Toast - { - public CopyUrlToast(LocalisableString value) - : base(UserInterfaceStrings.GeneralHeader, value, "") - { - } - } - private void copyUrl() { - if (Link != null) - { - host.GetClipboard()?.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.CopiedUrl)); - } + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); } public MenuItem[] ContextMenuItems @@ -109,5 +98,13 @@ namespace osu.Game.Graphics.UserInterface } public LocalisableString TooltipText => "view in browser"; + + private class CopyUrlToast : Toast + { + public CopyUrlToast(LocalisableString value) + : base(UserInterfaceStrings.GeneralHeader, value, "") + { + } + } } } From f01c3972207ca20b26a7e6361e2b492e3d44eeb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:12:46 +0900 Subject: [PATCH 14/40] Apply nullability --- .../Graphics/UserInterface/ExternalLinkButton.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 762b4bd90d..7b9abf6781 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -23,19 +21,19 @@ namespace osu.Game.Graphics.UserInterface { public class ExternalLinkButton : CompositeDrawable, IHasTooltip, IHasContextMenu { - public string Link { get; set; } + public string? Link { get; set; } private Color4 hoverColour; [Resolved] - private GameHost host { get; set; } + private GameHost host { get; set; } = null!; - [Resolved(canBeNull: true)] - private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } private readonly SpriteIcon linkIcon; - public ExternalLinkButton(string link = null) + public ExternalLinkButton(string? link = null) { Link = link; Size = new Vector2(12); From f44a4c865293ae77f6b0bf2255f7b976c6a6ea6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:13:45 +0900 Subject: [PATCH 15/40] Reorder file content to match expectations --- .../UserInterface/ExternalLinkButton.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 7b9abf6781..7f86a060ad 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -48,28 +48,6 @@ namespace osu.Game.Graphics.UserInterface }; } - private void copyUrl() - { - host.GetClipboard()?.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); - } - - public MenuItem[] ContextMenuItems - { - get - { - List items = new List(); - - if (Link != null) - { - items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); - } - - return items.ToArray(); - } - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -97,6 +75,28 @@ namespace osu.Game.Graphics.UserInterface public LocalisableString TooltipText => "view in browser"; + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (Link != null) + { + items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); + } + + return items.ToArray(); + } + } + + private void copyUrl() + { + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); + } + private class CopyUrlToast : Toast { public CopyUrlToast(LocalisableString value) From 8da499fb0fc1b1e0febcac88cccda792707a5da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:16:21 +0900 Subject: [PATCH 16/40] Add proper test coverage --- .../Visual/Online/TestSceneExternalLinkButton.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs index fdcde0f2a5..4185d56833 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs @@ -3,6 +3,8 @@ #nullable disable +using osu.Framework.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osuTK; @@ -12,9 +14,15 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneExternalLinkButton() { - Child = new ExternalLinkButton("https://osu.ppy.sh/home") + Child = new OsuContextMenuContainer { - Size = new Vector2(50) + RelativeSizeAxes = Axes.Both, + Child = new ExternalLinkButton("https://osu.ppy.sh/home") + { + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }; } } From 67c7f324ee899d652e127903a82917c980896e95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 19:02:43 +0900 Subject: [PATCH 17/40] Simplify `CollectionFilterDropdown` filter flow weirdness --- .../SongSelect/TestSceneFilterControl.cs | 10 +++++ .../Collections/CollectionFilterDropdown.cs | 43 +++++++------------ osu.Game/Screens/Select/FilterControl.cs | 20 +++------ 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index aaaa0aec95..feb71def5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -176,6 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestManageCollectionsFilterIsNotSelected() { + bool received = false; + addExpandHeaderStep(); AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); @@ -187,6 +189,12 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); + AddStep("watch for filter requests", () => + { + received = false; + control.ChildrenOfType().First().RequestFilter = () => received = true; + }); + AddStep("click manage collections filter", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); @@ -194,6 +202,8 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any()); + + AddAssert("filter request not fired", () => !received); } private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ec409df4d6..fa15e2b56f 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -30,15 +30,8 @@ namespace osu.Game.Collections /// protected virtual bool ShowManageCollectionsItem => true; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + public Action? RequestFilter { private get; set; } - public new Bindable Current - { - get => current.Current; - set => current.Current = value; - } - - private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved] @@ -50,6 +43,7 @@ namespace osu.Game.Collections public CollectionFilterDropdown() { ItemSource = filters; + Current.Value = new AllBeatmapsCollectionFilterMenuItem(); } @@ -59,17 +53,9 @@ namespace osu.Game.Collections realm.RegisterForNotifications(r => r.All(), collectionsChanged); - // Dropdown has logic which triggers a change on the bindable with every change to the contained items. - // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. - // An extra bindable is enough to subvert this behaviour. - base.Current = Current; - - Current.BindValueChanged(currentChanged, true); + Current.BindValueChanged(currentChanged); } - /// - /// Occurs when a collection has been added or removed. - /// private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; @@ -83,38 +69,41 @@ namespace osu.Game.Collections Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; - // Trigger a re-filter if the current item was in the changeset. + // Trigger a re-filter if the current item was in the change set. if (selectedItem != null && changes != null) { foreach (int index in changes.ModifiedIndices) { if (collections[index].ID == selectedItem.ID) - { - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } + RequestFilter?.Invoke(); } } } private void currentChanged(ValueChangedEvent filter) { + // May be null during .Clear(). + if (filter.NewValue == null) + return; + // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. if (filter.NewValue is ManageCollectionsFilterMenuItem) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); + return; } + + // This dropdown be weird. + // We only care about filtering if the actual collection has changed. + if (filter.OldValue?.Collection != null || filter.NewValue?.Collection != null) + RequestFilter?.Invoke(); } protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; - protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => - { - d.SelectedItem.BindTarget = Current; - }); + protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => d.SelectedItem.BindTarget = Current); protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu(); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 5f5344a338..f07817d5dc 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -39,6 +39,10 @@ namespace osu.Game.Screens.Select private Bindable groupMode; + private SeekLimitedSearchTextBox searchTextBox; + + private CollectionFilterDropdown collectionDropdown; + public FilterCriteria CreateCriteria() { string query = searchTextBox.Text; @@ -49,7 +53,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - CollectionBeatmapMD5Hashes = collectionDropdown?.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) + CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) }; if (!minimumStars.IsDefault) @@ -64,10 +68,6 @@ namespace osu.Game.Screens.Select return criteria; } - private SeekLimitedSearchTextBox searchTextBox; - - private CollectionFilterDropdown collectionDropdown; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + RequestFilter = updateCriteria, RelativeSizeAxes = Axes.X, Y = 4, Width = 0.5f, @@ -209,15 +210,6 @@ namespace osu.Game.Screens.Select groupMode.BindValueChanged(_ => updateCriteria()); sortMode.BindValueChanged(_ => updateCriteria()); - collectionDropdown.Current.ValueChanged += val => - { - if (val.NewValue == null) - // may be null briefly while menu is repopulated. - return; - - updateCriteria(); - }; - searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); From 34a2d1a6e1921baa76a8521ffbfa7d3b4d30f7fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 19:35:25 +0900 Subject: [PATCH 18/40] Fix `ManageCollectionsDialog` and remove weird placeholder logic --- .../TestSceneManageCollectionsDialog.cs | 27 +++++- .../Collections/CollectionFilterDropdown.cs | 33 ++++--- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 30 ++++-- .../Collections/DrawableCollectionListItem.cs | 97 ++++++------------- .../Collections/ManageCollectionsDialog.cs | 4 +- 6 files changed, 103 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 8de38eb4e7..afcb511a6a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -109,10 +109,15 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); - // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.PerformWrite(c => c.Name = "a")); + assertCollectionCount(0); + + AddStep("change collection name", () => + { + placeholderItem.ChildrenOfType().First().Text = "test text"; + InputManager.Key(Key.Enter); + }); + assertCollectionCount(1); - AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } @@ -257,6 +262,7 @@ namespace osu.Game.Tests.Visual.Collections public void TestCollectionRenamedOnTextChange() { BeatmapCollection first = null!; + DrawableCollectionListItem firstItem = null!; AddStep("add two collections", () => { @@ -272,12 +278,23 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionCount(2); - AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); + AddStep("focus first collection", () => + { + InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("change first collection name", () => + { + firstItem.ChildrenOfType().First().Text = "First"; + InputManager.Key(Key.Enter); + }); + AddUntilStep("collection has new name", () => first.Name == "First"); } private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index fa15e2b56f..790a23d2e5 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -53,21 +53,27 @@ namespace osu.Game.Collections realm.RegisterForNotifications(r => r.All(), collectionsChanged); - Current.BindValueChanged(currentChanged); + Current.BindValueChanged(selectionChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; + var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem(); + filters.Clear(); - filters.Add(new AllBeatmapsCollectionFilterMenuItem()); + filters.Add(allBeatmaps); filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; + // This current update and schedule is required to work around dropdown headers not updating text even when the selected item + // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue + // a warning that it's going to be a frustrating journey. + Current.Value = allBeatmaps; + Schedule(() => Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]); // Trigger a re-filter if the current item was in the change set. if (selectedItem != null && changes != null) @@ -80,7 +86,9 @@ namespace osu.Game.Collections } } - private void currentChanged(ValueChangedEvent filter) + private Live? lastFiltered; + + private void selectionChanged(ValueChangedEvent filter) { // May be null during .Clear(). if (filter.NewValue == null) @@ -95,15 +103,20 @@ namespace osu.Game.Collections return; } + var newCollection = filter.NewValue?.Collection; + // This dropdown be weird. // We only care about filtering if the actual collection has changed. - if (filter.OldValue?.Collection != null || filter.NewValue?.Collection != null) + if (newCollection != lastFiltered) + { RequestFilter?.Invoke(); + lastFiltered = newCollection; + } } protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; - protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => d.SelectedItem.BindTarget = Current); + protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader(); protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu(); @@ -113,8 +126,6 @@ namespace osu.Game.Collections public class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); - public CollectionDropdownHeader() { Height = 25; @@ -130,14 +141,14 @@ namespace osu.Game.Collections MaxHeight = 200; } - protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item) + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownDrawableMenuItem(item) { BackgroundColourHover = HoverColour, BackgroundColourSelected = SelectionColour }; } - protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + protected class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; @@ -148,7 +159,7 @@ namespace osu.Game.Collections [Resolved] private IBindable beatmap { get; set; } = null!; - public CollectionDropdownMenuItem(MenuItem item) + public CollectionDropdownDrawableMenuItem(MenuItem item) : base(item) { } diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 7594978870..f3f038a7f0 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -4,16 +4,17 @@ using System; using Humanizer; using osu.Framework.Graphics.Sprites; +using osu.Game.Database; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(Live collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; + BodyText = collection.PerformRead(c => $"{c.Name} ({"beatmap".ToQuantity(c.BeatmapMD5Hashes.Count)})"); Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f376d18224..1639afd362 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -1,33 +1,51 @@ // 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.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; +using Realms; namespace osu.Game.Collections { /// /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer> { private Scroll scroll = null!; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + [Resolved] + private RealmAccess realm { get; set; } = null!; + + protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; - // TODO: source from realm + protected override void LoadComplete() + { + base.LoadComplete(); - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + realm.RegisterForNotifications(r => r.All(), collectionsChanged); + } + + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) + { + Items.Clear(); + Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + } + + protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) { if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); @@ -97,7 +115,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection().ToLiveUnmanaged(), false)); return previous; } @@ -106,7 +124,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer>> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 6093e69deb..d1e40f6262 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -3,12 +3,12 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Database; using osu.Game.Graphics; @@ -23,52 +23,37 @@ namespace osu.Game.Collections /// /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem> { private const float item_height = 35; private const float button_width = item_height * 0.75f; - /// - /// Whether the currently exists inside realm. - /// - public IBindable IsCreated => isCreated; - - private readonly Bindable isCreated = new Bindable(); - /// /// Creates a new . /// /// The . /// Whether currently exists inside realm. - public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) + public DrawableCollectionListItem(Live item, bool isCreated) : base(item) { - this.isCreated.Value = isCreated; - - ShowDragHandle.BindTo(this.isCreated); + ShowDragHandle.Value = item.IsManaged; } - protected override Drawable CreateContent() => new ItemContent(Model) - { - IsCreated = { BindTarget = isCreated } - }; + protected override Drawable CreateContent() => new ItemContent(Model); /// /// The main content of the . /// private class ItemContent : CircularContainer { - public readonly Bindable IsCreated = new Bindable(); + private readonly Live collection; - private readonly BeatmapCollection collection; - - private Container textBoxPaddingContainer = null!; private ItemTextBox textBox = null!; [Resolved] private RealmAccess realm { get; set; } = null!; - public ItemContent(BeatmapCollection collection) + public ItemContent(Live collection) { this.collection = collection; @@ -80,19 +65,20 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Children = new[] { - new DeleteButton(collection) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - IsCreated = { BindTarget = IsCreated }, - IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) - }, - textBoxPaddingContainer = new Container + collection.IsManaged + ? new DeleteButton(collection) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + } + : Empty(), + new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = button_width }, + Padding = new MarginPadding { Right = collection.IsManaged ? button_width : 0 }, Children = new Drawable[] { textBox = new ItemTextBox @@ -100,7 +86,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - PlaceholderText = IsCreated.Value ? string.Empty : "Create a new collection" + PlaceholderText = collection.IsManaged ? string.Empty : "Create a new collection" }, } }, @@ -112,28 +98,18 @@ namespace osu.Game.Collections base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current.Value = collection.Name; - textBox.Current.BindValueChanged(_ => createNewCollection(), true); - - IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); + textBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); + textBox.OnCommit += onCommit; } - private void createNewCollection() + private void onCommit(TextBox sender, bool newText) { - if (IsCreated.Value) - return; + if (collection.IsManaged) + collection.PerformWrite(c => c.Name = textBox.Current.Value); + else if (!string.IsNullOrEmpty(textBox.Current.Value)) + realm.Write(r => r.Add(new BeatmapCollection(textBox.Current.Value))); - if (string.IsNullOrEmpty(textBox.Current.Value)) - return; - - // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - realm.Write(r => r.Add(collection)); - textBox.PlaceholderText = string.Empty; - - // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. - Schedule(() => GetContainingInputManager().ChangeFocus(textBox)); - - IsCreated.Value = true; + textBox.Text = string.Empty; } } @@ -151,22 +127,17 @@ namespace osu.Game.Collections public class DeleteButton : CompositeDrawable { - public readonly IBindable IsCreated = new Bindable(); - public Func IsTextBoxHovered = null!; [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - [Resolved] - private RealmAccess realmAccess { get; set; } = null!; - - private readonly BeatmapCollection collection; + private readonly Live collection; private Drawable fadeContainer = null!; private Drawable background = null!; - public DeleteButton(BeatmapCollection collection) + public DeleteButton(Live collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; @@ -200,12 +171,6 @@ namespace osu.Game.Collections }; } - protected override void LoadComplete() - { - base.LoadComplete(); - IsCreated.BindValueChanged(created => Alpha = created.NewValue ? 1 : 0, true); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); protected override bool OnHover(HoverEvent e) @@ -223,7 +188,7 @@ namespace osu.Game.Collections { background.FlashColour(Color4.White, 150); - if (collection.BeatmapMD5Hashes.Count == 0) + if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) deleteCollection(); else dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); @@ -231,7 +196,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => realmAccess.Write(r => r.Remove(collection)); + private void deleteCollection() => collection.PerformWrite(c => c.Realm.Remove(c)); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 721e0d632e..13737dbd78 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -23,7 +21,7 @@ namespace osu.Game.Collections private const double enter_duration = 500; private const double exit_duration = 200; - private AudioFilter lowPassFilter; + private AudioFilter lowPassFilter = null!; public ManageCollectionsDialog() { From 1669208a54a232dc2e097bc129b3839209be3b2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 23:19:00 +0900 Subject: [PATCH 19/40] Add migration of existing collections database --- osu.Game/Database/LegacyCollectionImporter.cs | 14 +++++------ osu.Game/Database/LegacyImportManager.cs | 2 +- osu.Game/Database/RealmAccess.cs | 25 ++++++++++++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index bd32b8c446..4bb28bf731 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -7,8 +7,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Collections; -using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -27,14 +27,14 @@ namespace osu.Game.Database this.realm = realm; } - public Task GetAvailableCount(StableStorage stableStorage) + public Task GetAvailableCount(Storage storage) { - if (!stableStorage.Exists(database_name)) + if (!storage.Exists(database_name)) return Task.FromResult(0); return Task.Run(() => { - using (var stream = stableStorage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) return readCollections(stream).Count; }); } @@ -42,9 +42,9 @@ namespace osu.Game.Database /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync(StableStorage stableStorage) + public Task ImportFromStorage(Storage storage) { - if (!stableStorage.Exists(database_name)) + if (!storage.Exists(database_name)) { // This handles situations like when the user does not have a collections.db file Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); @@ -53,7 +53,7 @@ namespace osu.Game.Database return Task.Run(async () => { - using (var stream = stableStorage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) await Import(stream).ConfigureAwait(false); }); } diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index baa117fe07..96f4aaf67c 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index a93fdea35b..6a0d4d34db 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Development; +using osu.Framework.Extensions; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; @@ -64,8 +65,9 @@ namespace osu.Game.Database /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. + /// 21 2022-07-27 Migrate collections to realm (BeatmapCollection). /// - private const int schema_version = 20; + private const int schema_version = 21; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -790,6 +792,27 @@ namespace osu.Game.Database beatmap.StarRating = -1; break; + + case 21: + try + { + // Migrate collections from external file to inside realm. + // We use the "legacy" importer because that is how things were actually being saved out until now. + var legacyCollectionImporter = new LegacyCollectionImporter(this); + + if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) + { + legacyCollectionImporter.ImportFromStorage(storage); + storage.Delete("collection.db"); + } + } + catch (Exception e) + { + // can be removed 20221027 (just for initial safety). + Logger.Error(e, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team."); + } + + break; } } From 226eefcc5c0a87d26962b4c22b68b2e53211535e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:37:01 +0900 Subject: [PATCH 20/40] Add note about hash storage --- osu.Game/Collections/BeatmapCollection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 2ffe17d9e6..ca5f8dbe53 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -26,6 +26,13 @@ namespace osu.Game.Collections /// /// The es of beatmaps contained by the collection. /// + /// + /// We store as hashes rather than references to s to allow collections to maintain + /// references to beatmaps even if they are removed. This helps with cases like importing collections before + /// importing the beatmaps they contain, or when sharing collections between users. + /// + /// This can probably change in the future as we build the system up. + /// public IList BeatmapMD5Hashes { get; } = null!; /// From ad482b8afcb23dedba8939b0ebaf7c960c4f5c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:48:15 +0900 Subject: [PATCH 21/40] Tidy up naming of collection dropdowns --- .../TestSceneManageCollectionsDialog.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 4 +-- .../SongSelect/TestSceneFilterControl.cs | 8 ++--- ...ilterDropdown.cs => CollectionDropdown.cs} | 33 +++++++++++-------- osu.Game/Overlays/Music/FilterControl.cs | 4 +-- ...own.cs => NowPlayingCollectionDropdown.cs} | 4 +-- osu.Game/Screens/Select/FilterControl.cs | 4 +-- 7 files changed, 33 insertions(+), 26 deletions(-) rename osu.Game/Collections/{CollectionFilterDropdown.cs => CollectionDropdown.cs} (89%) rename osu.Game/Overlays/Music/{CollectionDropdown.cs => NowPlayingCollectionDropdown.cs} (93%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index afcb511a6a..6a88ce1ba6 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add dropdown", () => { - Add(new CollectionFilterDropdown + Add(new CollectionDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a61352f954..58898d8386 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set filter again", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); AddStep("open collections dropdown", () => { - InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); + InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddStep("press back once", () => InputManager.Click(MouseButton.Button1)); AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); AddAssert("collections dropdown closed", () => songSelect - .ChildrenOfType().Single() + .ChildrenOfType().Single() .ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed); AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index feb71def5d..a07bfaee2a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { - var dropdown = control.ChildrenOfType().Single(); + var dropdown = control.ChildrenOfType().Single(); dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); }); @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", - () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); + () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", @@ -222,7 +222,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addExpandHeaderStep() => AddStep("expand header", () => { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); @@ -233,6 +233,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs similarity index 89% rename from osu.Game/Collections/CollectionFilterDropdown.cs rename to osu.Game/Collections/CollectionDropdown.cs index 790a23d2e5..197e0d1837 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -21,9 +21,9 @@ using Realms; namespace osu.Game.Collections { /// - /// A dropdown to select the to filter beatmaps using. + /// A dropdown to select the collection to be used to filter results. /// - public class CollectionFilterDropdown : OsuDropdown + public class CollectionDropdown : OsuDropdown { /// /// Whether to show the "manage collections..." menu item in the dropdown. @@ -40,7 +40,9 @@ namespace osu.Game.Collections [Resolved] private RealmAccess realm { get; set; } = null!; - public CollectionFilterDropdown() + private IDisposable? realmSubscription; + + public CollectionDropdown() { ItemSource = filters; @@ -51,7 +53,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); Current.BindValueChanged(selectionChanged); } @@ -114,6 +116,12 @@ namespace osu.Game.Collections } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } + protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader(); @@ -150,18 +158,19 @@ namespace osu.Game.Collections protected class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { - protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; - private IconButton addOrRemoveButton = null!; private bool beatmapInCollection; + private readonly Live? collection; + [Resolved] private IBindable beatmap { get; set; } = null!; public CollectionDropdownDrawableMenuItem(MenuItem item) : base(item) { + collection = ((DropdownMenuItem)item).Value.Collection; } [BackgroundDependencyLoader] @@ -181,13 +190,11 @@ namespace osu.Game.Collections { base.LoadComplete(); - if (Item.Collection != null) + if (collection != null) { beatmap.BindValueChanged(_ => { - Debug.Assert(Item.Collection != null); - - beatmapInCollection = Item.Collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); + beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -220,7 +227,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (Item.Collection == null) + if (collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -228,9 +235,9 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(Item.Collection != null); + Debug.Assert(collection != null); - Item.Collection.PerformWrite(c => + collection.PerformWrite(c => { if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index eb12a62864..ffa50c3a35 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Music public Action FilterChanged; public readonly FilterTextBox Search; - private readonly CollectionDropdown collectionDropdown; + private readonly NowPlayingCollectionDropdown collectionDropdown; public FilterControl() { @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Music RelativeSizeAxes = Axes.X, Height = 40, }, - collectionDropdown = new CollectionDropdown { RelativeSizeAxes = Axes.X } + collectionDropdown = new NowPlayingCollectionDropdown { RelativeSizeAxes = Axes.X } }, }, }; diff --git a/osu.Game/Overlays/Music/CollectionDropdown.cs b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs similarity index 93% rename from osu.Game/Overlays/Music/CollectionDropdown.cs rename to osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs index c1ba16788e..635a2e5044 100644 --- a/osu.Game/Overlays/Music/CollectionDropdown.cs +++ b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs @@ -15,9 +15,9 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Music { /// - /// A for use in the . + /// A for use in the . /// - public class CollectionDropdown : CollectionFilterDropdown + public class NowPlayingCollectionDropdown : CollectionDropdown { protected override bool ShowManageCollectionsItem => false; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index f07817d5dc..ae82285fba 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select private SeekLimitedSearchTextBox searchTextBox; - private CollectionFilterDropdown collectionDropdown; + private CollectionDropdown collectionDropdown; public FilterCriteria CreateCriteria() { @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Width = 0.48f, }, - collectionDropdown = new CollectionFilterDropdown + collectionDropdown = new CollectionDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From da0646789120f33153946618d0ed2e414171bb17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:50:19 +0900 Subject: [PATCH 22/40] Add missing realm subscription cleanup --- osu.Game/Collections/DrawableCollectionList.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 1639afd362..8546ba53c2 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -20,13 +20,15 @@ namespace osu.Game.Collections /// public class DrawableCollectionList : OsuRearrangeableListContainer> { - private Scroll scroll = null!; - protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); [Resolved] private RealmAccess realm { get; set; } = null!; + private Scroll scroll = null!; + + private IDisposable? realmSubscription; + protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } @@ -36,7 +38,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) @@ -53,6 +55,12 @@ namespace osu.Game.Collections return new DrawableCollectionListItem(item, true); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } + /// /// The scroll container for this . /// Contains the main flow of and attaches a placeholder item to the end of the list. From 392cb352cc71da8b0f82aa0877ea6a7febfc54b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 14:07:42 +0900 Subject: [PATCH 23/40] Force alphabetical ordering for now --- osu.Game/Collections/CollectionDropdown.cs | 2 +- osu.Game/Collections/DrawableCollectionList.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 197e0d1837..43a4d90aa8 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -53,7 +53,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); Current.BindValueChanged(selectionChanged); } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 8546ba53c2..0f4362fff3 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) From ca6857447345d4dc35aadeba1241f91b63dcc645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 14:35:27 +0900 Subject: [PATCH 24/40] Make `NotificationOverlay` dependency optional in `CollectionSettings` --- .../Settings/Sections/Maintenance/CollectionsSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 498859db46..5a91213eb8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private RealmAccess realm { get; set; } = null!; [Resolved] - private INotificationOverlay notificationOverlay { get; set; } = null!; + private INotificationOverlay? notificationOverlay { get; set; } [BackgroundDependencyLoader] private void load(LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { realm.Write(r => r.RemoveAll()); - notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } } } From 2ae5a34c0e3ca9cca42b2880e5f9c3300a40a2d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:02:58 +0900 Subject: [PATCH 25/40] Add test coverage of beatmap updates transferring collection hashes --- .../Database/BeatmapImporterUpdateTests.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index a82386fd51..90648c616e 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -9,6 +9,7 @@ using System.Linq.Expressions; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Models; using osu.Game.Overlays.Notifications; @@ -400,6 +401,122 @@ namespace osu.Game.Tests.Database }); } + /// + /// If all difficulties in the original beatmap set are in a collection, presume the user also wants new difficulties added. + /// + [TestCase(false)] + [TestCase(true)] + public void TestCollectionTransferNewBeatmap(bool allOriginalBeatmapsInCollection) + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // remove one difficulty before first import + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + int beatmapsToAddToCollection = 0; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + beatmapsToAddToCollection = s.Beatmaps.Count - (allOriginalBeatmapsInCollection ? 0 : 1); + + for (int i = 0; i < beatmapsToAddToCollection; i++) + beatmapCollection.BeatmapMD5Hashes.Add(s.Beatmaps[i].MD5Hash); + }); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + importAfterUpdate.PerformRead(updated => + { + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); + + if (allOriginalBeatmapsInCollection) + { + Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 1)); + Assert.That(hashes, Has.Length.EqualTo(updated.Beatmaps.Count)); + } + else + { + // Collection contains one less than the original beatmap, and two less after update (new difficulty included). + Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 2)); + Assert.That(hashes, Has.Length.EqualTo(beatmapsToAddToCollection)); + } + }); + }); + } + + /// + /// If a difficulty in the original beatmap set is modified, the updated version should remain in any collections it was in. + /// + [Test] + public void TestCollectionTransferModifiedBeatmap() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => + { + // Modify one .osu file with different content. + var firstOsuFile = directory.GetFiles("*[Hard]*.osu").First(); + + string existingContent = File.ReadAllText(firstOsuFile.FullName); + + File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content"); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + string originalHash = string.Empty; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + originalHash = s.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; + + beatmapCollection.BeatmapMD5Hashes.Add(originalHash); + }); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + importAfterUpdate.PerformRead(updated => + { + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); + string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; + + Assert.That(hashes, Has.Length.EqualTo(1)); + Assert.That(hashes.First(), Is.EqualTo(updatedHash)); + + Assert.That(updatedHash, Is.Not.EqualTo(originalHash)); + }); + }); + } + private static void checkCount(RealmAccess realm, int expected, Expression>? condition = null) where T : RealmObject { var query = realm.Realm.All(); From 2209afd0e8c82405701b8b6807d9a280cbf1aacc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:03:08 +0900 Subject: [PATCH 26/40] Mark `Live` methods as `InstantHandleAttribute` --- osu.Game/Database/Live.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index e9f99e1e44..52e1d420f7 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; namespace osu.Game.Database { @@ -18,19 +19,19 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public abstract void PerformRead(Action perform); + public abstract void PerformRead([InstantHandle] Action perform); /// /// Perform a read operation on this live object. /// /// The action to perform. - public abstract TReturn PerformRead(Func perform); + public abstract TReturn PerformRead([InstantHandle] Func perform); /// /// Perform a write operation on this live object. /// /// The action to perform. - public abstract void PerformWrite(Action perform); + public abstract void PerformWrite([InstantHandle] Action perform); /// /// Whether this instance is tracking data which is managed by the database backing. From 070f56c30ce0ce51b3d30781b36fca5bf64e52a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:03:23 +0900 Subject: [PATCH 27/40] Add collection transfer logic to beatmap import-as-update flow --- osu.Game/Beatmaps/BeatmapImporter.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index ef0e76234a..d1f7a5c6a8 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -14,6 +14,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; @@ -71,6 +72,8 @@ namespace osu.Game.Beatmaps // Transfer local values which should be persisted across a beatmap update. updated.DateAdded = original.DateAdded; + transferCollectionReferences(realm, original, updated); + foreach (var beatmap in original.Beatmaps.ToArray()) { var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); @@ -112,6 +115,40 @@ namespace osu.Game.Beatmaps return first; } + private static void transferCollectionReferences(Realm realm, BeatmapSetInfo original, BeatmapSetInfo updated) + { + // First check if every beatmap in the original set is in any collections. + // In this case, we will assume they also want any newly added difficulties added to the collection. + foreach (var c in realm.All()) + { + if (original.Beatmaps.Select(b => b.MD5Hash).All(c.BeatmapMD5Hashes.Contains)) + { + foreach (var b in original.Beatmaps) + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + + foreach (var b in updated.Beatmaps) + c.BeatmapMD5Hashes.Add(b.MD5Hash); + } + } + + // Handle collections using permissive difficulty name to track difficulties. + foreach (var originalBeatmap in original.Beatmaps) + { + var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalBeatmap.DifficultyName); + + if (updatedBeatmap == null) + continue; + + var collections = realm.All().AsEnumerable().Where(c => c.BeatmapMD5Hashes.Contains(originalBeatmap.MD5Hash)); + + foreach (var c in collections) + { + c.BeatmapMD5Hashes.Remove(originalBeatmap.MD5Hash); + c.BeatmapMD5Hashes.Add(updatedBeatmap.MD5Hash); + } + } + } + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) From 485d140a218fa92d5b800be9997104f71c831494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:15:41 +0900 Subject: [PATCH 28/40] Add realm refresh calls to fix intermittent test failures on new update tests --- .../Database/BeatmapImporterUpdateTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index fdc9f2569d..81baeddbd7 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -51,6 +51,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); @@ -60,6 +62,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 1); @@ -103,6 +107,8 @@ namespace osu.Game.Tests.Database beatmap.ResetOnlineInfo(); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); @@ -112,6 +118,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 1); @@ -148,6 +156,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); @@ -161,6 +171,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1)); Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); @@ -198,6 +210,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 2); @@ -234,6 +248,8 @@ namespace osu.Game.Tests.Database var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathEmpty), importBeforeUpdate.Value); Assert.That(importAfterUpdate, Is.Null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); @@ -263,6 +279,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); @@ -307,6 +325,8 @@ namespace osu.Game.Tests.Database s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value); @@ -314,6 +334,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 2); @@ -348,6 +370,8 @@ namespace osu.Game.Tests.Database s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => @@ -365,6 +389,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); checkCount(realm, 2); From 67c44552cb25f00ae7b46d87fb811956b5e4902b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:18:04 +0900 Subject: [PATCH 29/40] Add realm `Refresh` calls to ensure tests are stable --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 90648c616e..07bd08d326 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -444,6 +444,8 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { + updated.Realm.Refresh(); + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); if (allOriginalBeatmapsInCollection) @@ -506,6 +508,8 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { + updated.Realm.Refresh(); + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; From 8ac886a247c9cb1d25d8eba6c8048b6654561d2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:20:25 +0900 Subject: [PATCH 30/40] Update test to account for sort order --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 6a88ce1ba6..13393254a6 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -253,9 +253,14 @@ namespace osu.Game.Tests.Visual.Collections }); }); + assertCollectionName(0, "1"); + assertCollectionName(1, "1"); + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); - assertCollectionName(0, "First"); + // Item will have moved due to alphabetical sorting. + assertCollectionName(0, "2"); + assertCollectionName(1, "First"); } [Test] From 452d82f29239337af44a287e3b14d4e61730496f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:41:28 +0900 Subject: [PATCH 31/40] Add more comprehensive xmldoc for beatmap model classes --- osu.Game/Beatmaps/Beatmap.cs | 3 --- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Beatmaps/BeatmapMetadata.cs | 11 +++++++++++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 3 +++ osu.Game/Beatmaps/IBeatmap.cs | 7 +++++++ osu.Game/Beatmaps/IWorkingBeatmap.cs | 7 ++++++- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index c499bccb68..2d02fb6200 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -14,9 +14,6 @@ using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps { - /// - /// A Beatmap containing converted HitObjects. - /// public class Beatmap : IBeatmap where T : HitObject { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 66c1995c8b..f368f369ae 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -20,8 +20,12 @@ using Realms; namespace osu.Game.Beatmaps { /// - /// A single beatmap difficulty. + /// A realm model containing metadata for a single beatmap difficulty. + /// This should generally include anything which is required to be filtered on at song select, or anything pertaining to storage of beatmaps in the client. /// + /// + /// There are some legacy fields in this model which are not persisted to realm. These are isolated in a code region within the class and should eventually be migrated to `Beatmap`. + /// [ExcludeFromDynamicCompile] [Serializable] [MapTo("Beatmap")] diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index feb9d34f44..f645d914b1 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -12,6 +12,17 @@ using Realms; namespace osu.Game.Beatmaps { + /// + /// A realm model containing metadata for a beatmap. + /// + /// + /// This is currently stored against each beatmap difficulty, even when it is duplicated. + /// It is also provided via for convenience and historical purposes. + /// A future effort could see this converted to an or potentially de-duped + /// and shared across multiple difficulties in the same set, if required. + /// + /// Note that difficulty name is not stored in this metadata but in . + /// [ExcludeFromDynamicCompile] [Serializable] [MapTo("BeatmapMetadata")] diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index b404f0b34d..7f65d1291d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -14,6 +14,9 @@ using Realms; namespace osu.Game.Beatmaps { + /// + /// A realm model containing metadata for a beatmap set (containing multiple ). + /// [ExcludeFromDynamicCompile] [MapTo("BeatmapSet")] public class BeatmapSetInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 25b147c267..0e892b6581 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -11,6 +11,10 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Beatmaps { + /// + /// A materialised beatmap. + /// Generally this interface will be implemented alongside , which exposes the ruleset-typed hit objects. + /// public interface IBeatmap { /// @@ -65,6 +69,9 @@ namespace osu.Game.Beatmaps IBeatmap Clone(); } + /// + /// A materialised beatmap containing converted HitObjects. + /// public interface IBeatmap : IBeatmap where T : HitObject { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 2188bd6a2b..548341cc77 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -18,7 +18,12 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps { /// - /// Provides access to the multiple resources offered by a beatmap model (textures, skins, playable beatmaps etc.) + /// A more expensive representation of a beatmap which allows access to various associated resources. + /// - Access textures and other resources via . + /// - Access the storyboard via . + /// - Access a local skin via . + /// - Access the track via (and then for subsequent accesses). + /// - Create a playable via . /// public interface IWorkingBeatmap { From 6bf293e130b98c35f7c896d8f5e174b0b436f04b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 15:45:32 +0900 Subject: [PATCH 32/40] Fix managed object reused between test runs --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index a07bfaee2a..d3b3238bb0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -78,9 +78,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - var first = new BeatmapCollection(name: "1"); + BeatmapCollection first = null!; - AddStep("add collection", () => Realm.Write(r => r.Add(first))); + AddStep("add collection", () => Realm.Write(r => r.Add(first = new BeatmapCollection(name: "1")))); AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); From 525e4a20193a8f5d644595fecddea0f9ef516f97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:51:18 +0900 Subject: [PATCH 33/40] Fix crash in `DrawableRoomPlaylistItem` context menu creation due to incorrect enumeration casting --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 492bb8ada5..8dccc3d82f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); From c1aaf27c54bc2d996086c275c19556a7f163f9e5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:02:19 +0900 Subject: [PATCH 34/40] Link to correct model in xmldoc --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7f65d1291d..d27d9d9192 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -15,7 +15,7 @@ using Realms; namespace osu.Game.Beatmaps { /// - /// A realm model containing metadata for a beatmap set (containing multiple ). + /// A realm model containing metadata for a beatmap set (containing multiple s). /// [ExcludeFromDynamicCompile] [MapTo("BeatmapSet")] From 2b9d46d80392579086c0b26d77646d0f0e30cc10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 16:19:05 +0900 Subject: [PATCH 35/40] Remove unused `RulesetStore` from `BeatmapManager` constructor --- osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 5 ++--- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs | 5 ++--- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 5 ++--- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 5 ++--- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 5 ++--- .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 5 ++--- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 5 ++--- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 5 ++--- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 6 ++---- osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 5 ++--- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 5 ++--- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 5 ++--- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 5 ++--- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 24 files changed, 37 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 0348e47d4a..f14288e7ba 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); } [SetUpSteps] diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 536322805b..3f20f843a7 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Online public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) - : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) + : base(storage, realm, api, audioManager, resources, host, defaultBeatmap) { } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index aaccea09d4..5aadd6f56a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 3f30fa367c..8f2146dac5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Collections private DialogOverlay dialogOverlay; private CollectionManager manager; - private RulesetStore rulesets; private BeatmapManager beatmapManager; private ManageCollectionsDialog dialog; @@ -38,8 +37,8 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 6491987abe..ddb585a73c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2b461cf6f6..ca4d926866 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private TestMultiplayerComponents multiplayerComponents; @@ -45,8 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 1797c82fb9..73d1222156 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -38,13 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestPlaylist playlist; private BeatmapManager manager; - private RulesetStore rulesets; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bf9b99e3e2..269867be73 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -49,7 +49,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayer : ScreenTestScene { private BeatmapManager beatmaps = null!; - private RulesetStore rulesets = null!; private BeatmapSetInfo importedSet = null!; private TestMultiplayerComponents multiplayerComponents = null!; @@ -63,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index ab4f9c37b2..2281235f25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 5d6a6c8104..5ebafbaabb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiplayerMatchSubScreen screen; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; public TestSceneMultiplayerMatchSubScreen() @@ -51,8 +50,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 5ee385810b..75e6088b0d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -31,15 +31,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerPlaylist list; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index e709a955b3..f31261dc1f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -29,15 +29,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerQueueList playlist; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 91c87548c7..e70c414bef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -35,13 +35,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapSetInfo importedSet; private BeatmapManager beatmaps; - private RulesetStore rulesets; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 88afe1ce7c..2eddf1a17e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -28,15 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { private BeatmapManager manager; - private RulesetStore rulesets; - private TestPlaylistsSongSelect songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index d80537a2e5..ef2a431b8f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneTeamVersus : ScreenTestScene { private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private TestMultiplayerComponents multiplayerComponents; @@ -40,8 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index c0cd2d9157..e798f72891 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -32,7 +32,6 @@ namespace osu.Game.Tests.Visual.Playlists public class TestScenePlaylistsRoomCreation : OnlinePlayTestScene { private BeatmapManager manager; - private RulesetStore rulesets; private TestPlaylistsRoomSubScreen match; @@ -41,8 +40,8 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index abcb888cd4..aeb30c94e1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6807180640..b25e0f2b34 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.SongSelect private CollectionManager collectionManager; - private RulesetStore rulesets; private BeatmapManager beatmapManager; private FilterControl control; @@ -38,8 +37,8 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 159a3b1923..12f15b04dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.SongSelect // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 05b5c5c0cd..72d78ededb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index e59914f69a..3beade9d4f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly ContextMenuContainer contextMenuContainer; private readonly BeatmapLeaderboard leaderboard; - private RulesetStore rulesetStore; private BeatmapManager beatmapManager; private ScoreManager scoreManager; @@ -72,8 +71,8 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(new RealmRulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index debe4c6829..5db9311e7b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps public Action? ProcessBeatmap { private get; set; } - public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, + public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) : base(storage, realm) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7b6cda17a2..b399fdc175 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -271,7 +271,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 31036247ab..ced72aa593 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual public WorkingBeatmap TestBeatmap; public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) - : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) + : base(storage, realm, api, audioManager, resources, host, defaultBeatmap) { } From cd01c5d3acefc5678be6dd6f7e3653acdbce8fc7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:34:31 +0900 Subject: [PATCH 36/40] Fix assertion --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 13393254a6..4c89fc1ab9 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Collections }); assertCollectionName(0, "1"); - assertCollectionName(1, "1"); + assertCollectionName(1, "2"); AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); From 6d4023b933dd97a65b9f3abe977f1671de8b91d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:56:11 +0900 Subject: [PATCH 37/40] Adjust comment --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 07bd08d326..76339d4a1c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -500,7 +500,7 @@ namespace osu.Game.Tests.Database beatmapCollection.BeatmapMD5Hashes.Add(originalHash); }); - // Second import matches first but contains one extra .osu file. + // Second import matches first but contains a modified .osu file. var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); Assert.That(importAfterUpdate, Is.Not.Null); From 628a30193ff7b94c10815d1c8d8be7db7b1e4187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 17:49:17 +0900 Subject: [PATCH 38/40] Remove incorrect `TrackLoaded` override from `TestWorkingBeatmap` --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 2306fd1c3e..3d7ebad831 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -31,8 +31,6 @@ namespace osu.Game.Tests.Beatmaps this.storyboard = storyboard; } - public override bool TrackLoaded => true; - public override bool BeatmapLoaded => true; protected override IBeatmap GetBeatmap() => beatmap; From 1039338d804dd33faf12ec8bc96b361cad280de4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 17:58:07 +0900 Subject: [PATCH 39/40] Fix intermittent HUD tests --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 1 + osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index dd0f965914..fb97f94dbb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -159,6 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); + AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded)); AddStep("bind on update", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index f319290441..bd274dfef5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -14,6 +14,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning; using osu.Game.Skinning.Editor; using osuTK.Input; @@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddStep("reload skin editor", () => { skinEditor?.Expire(); From a21aee4e9cabcbb71fb74e0185c0cc62f3de737d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 17:58:13 +0900 Subject: [PATCH 40/40] Reduce calls to `LoadTrack` by implicitly running on test/dummy classes --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 6 ------ osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 -- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 7 +++++-- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 1 - osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 4 ++++ osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 1 - osu.Game/Tests/Visual/OsuTestScene.cs | 5 +++++ 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 0395ae9d99..5f403f9487 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -41,8 +41,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); @@ -58,8 +56,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); @@ -102,8 +98,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); gameplayClockContainer.Reset(startClock: !whileStopped); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index a9c6bacc65..a432cc9648 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -69,7 +69,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); Add(gameplayContainer = new MasterGameplayClockContainer(working, 0) { @@ -96,7 +95,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); const double start_time = 1000; diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index f4cea2c8cc..e82a5b57d9 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -32,7 +32,6 @@ namespace osu.Game.Tests.Skins imported?.PerformRead(s => { beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); - beatmap.LoadTrack(); }); } @@ -40,6 +39,10 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual)); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => + { + using (var track = beatmap.LoadTrack()) + return track is not TrackVirtual; + }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index e1fc65404d..5c73db15df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.Gameplay increment = skip_time; var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); - working.LoadTrack(); Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0) { diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 9610dbcc78..0b390a2ab5 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -44,6 +44,10 @@ namespace osu.Game.Beatmaps }, audio) { this.textures = textures; + + // We are guaranteed to have a virtual track. + // To ease usability, ensure the track is available from point of construction. + LoadTrack(); } protected override IBeatmap GetBeatmap() => new Beatmap(); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 58bfced3ed..0d4496a6a3 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -161,7 +161,6 @@ namespace osu.Game.Overlays.FirstRunSetup private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets) { Beatmap.Value = new DummyWorkingBeatmap(audio, textures); - Beatmap.Value.LoadTrack(); Ruleset.Value = rulesets.AvailableRulesets.First(); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 012c512266..5a297fd109 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -365,6 +365,11 @@ namespace osu.Game.Tests.Visual } else track = audio?.Tracks.GetVirtual(trackLength); + + // We are guaranteed to have a virtual track. + // To ease testability, ensure the track is available from point of construction. + // (Usually this would be done by MusicController for us). + LoadTrack(); } ~ClockBackedTestWorkingBeatmap()