diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cba7f34ede..fc2a3792cb 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - BeatmapSetInfo importedSet; + ILive importedSet; using (var stream = File.OpenRead(tempPath)) { @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); deleteBeatmapSet(imported, osu); } @@ -172,8 +172,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // but contents doesn't, so existing should still be used. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -226,8 +226,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -278,8 +278,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -329,8 +329,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -570,8 +570,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await manager.Import(toImport); Assert.NotNull(imported); - Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); - Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID); } finally { @@ -706,7 +706,7 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); } finally { @@ -759,8 +759,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); } finally { @@ -915,7 +915,7 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); } public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) @@ -930,7 +930,7 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); } private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index d38294aba9..79767bc671 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public Task CurrentImportTask { get; private set; } + public Task> CurrentImportTask { get; private set; } public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 7a9fc20426..b2600bb887 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Skins.IO private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(archive); + return (await skinManager.Import(archive)).Value; } } } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index eff430ac25..f03cda1489 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; - beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 107a96292f..10f1ab31df 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; - skin = skins.GetSkin(imported); + skin = skins.GetSkin(imported.Value); } [Test] diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9037338e23..79dfe79299 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("import beatmap with track", () => { var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; - Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); + Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); }); AddStep("bind to track change", () => diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f0ddefa51d..5f5ebfccfb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).Result; + }).Result.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 52b577b402..2ea765a1a9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).Result; + }).Result.Value; }); } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation OnlineScoreID = i, Beatmap = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo - }).Result; + }).Result.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 9051c71fc6..d8ec89a94e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Playlists { beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; - importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; + importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; }); AddStep("load room", () => diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53cb628bb3..c22b6a54e9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect }).ToList() }; - return Game.BeatmapManager.Import(beatmapSet).Result; + return Game.BeatmapManager.Import(beatmapSet).Result.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 102e5ee425..19aa91a38f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -751,7 +751,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); - imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result; + imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2e30ed9827..3c69db032e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); - beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; + beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0]; for (int i = 0; i < 50; i++) { @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface User = new User { Username = "TestUser" }, }; - importedScores.Add(scoreManager.Import(score).Result); + importedScores.Add(scoreManager.Import(score).Result.Value); } return dependencies; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8dfd895987..1bf4feb6a3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -89,8 +89,9 @@ namespace osu.Game.Beatmaps } }; - var working = beatmapModelManager.Import(set).Result; - return GetWorkingBeatmap(working.Beatmaps.First()); + var imported = beatmapModelManager.Import(set).Result.Value; + + return GetWorkingBeatmap(imported.Beatmaps.First()); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps /// /// Fired when the user requests to view the resulting import. /// - public Action> PresentImport { set => beatmapModelManager.PresentImport = value; } + public Action>> PresentImport { set => beatmapModelManager.PresentImport = value; } /// /// Delete a beatmap difficulty. @@ -275,22 +276,22 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(tasks); } - public Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return beatmapModelManager.Import(notification, tasks); } - public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(task, lowPriority, cancellationToken); } - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 0c309bbddb..403bfdf621 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -132,13 +132,13 @@ namespace osu.Game.Database return Import(notification, tasks); } - public async Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { if (tasks.Length == 0) { notification.CompletionText = $"No {HumanisedModelName}s were found to import!"; notification.State = ProgressNotificationState.Completed; - return Enumerable.Empty(); + return Enumerable.Empty>(); } notification.Progress = 0; @@ -146,7 +146,7 @@ namespace osu.Game.Database int current = 0; - var imported = new List(); + var imported = new List>(); bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; @@ -224,11 +224,11 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - public async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - TModel import; + ILive import; using (ArchiveReader reader = task.GetReader()) import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); @@ -243,13 +243,13 @@ namespace osu.Game.Database } catch (Exception e) { - LogForModel(import, $@"Could not delete original file after import ({task})", e); + LogForModel(import?.Value, $@"Could not delete original file after import ({task})", e); } return import; } - public Action> PresentImport { protected get; set; } + public Action>> PresentImport { protected get; set; } /// /// Silently import an item from an . @@ -257,7 +257,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -268,7 +268,7 @@ namespace osu.Game.Database model = CreateModel(archive); if (model == null) - return Task.FromResult(null); + return Task.FromResult>(new EntityFrameworkLive(null)); } catch (TaskCanceledException) { @@ -343,7 +343,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual async Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); @@ -369,7 +369,7 @@ namespace osu.Game.Database { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); - return existing; + return existing.ToEntityFrameworkLive(); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -415,7 +415,7 @@ namespace osu.Game.Database // existing item will be used; rollback new import and exit early. rollback(); flushEvents(true); - return existing; + return existing.ToEntityFrameworkLive(); } LogForModel(item, @"Found existing but failed re-use check."); @@ -448,7 +448,7 @@ namespace osu.Game.Database } flushEvents(true); - return item; + return item.ToEntityFrameworkLive(); }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); /// diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs new file mode 100644 index 0000000000..1d7b53911a --- /dev/null +++ b/osu.Game/Database/EntityFrameworkLive.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Database +{ + public class EntityFrameworkLive : ILive where T : class + { + public EntityFrameworkLive(T item) + { + Value = item; + } + + public Guid ID => throw new InvalidOperationException(); + + public void PerformRead(Action perform) + { + perform(Value); + } + + public TReturn PerformRead(Func perform) + { + return perform(Value); + } + + public void PerformWrite(Action perform) + { + perform(Value); + } + + public T Value { get; } + } +} diff --git a/osu.Game/Database/EntityFrameworkLiveExtensions.cs b/osu.Game/Database/EntityFrameworkLiveExtensions.cs new file mode 100644 index 0000000000..cd0673675e --- /dev/null +++ b/osu.Game/Database/EntityFrameworkLiveExtensions.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Database +{ + public static class EntityFrameworkLiveExtensions + { + public static ILive ToEntityFrameworkLive(this T item) + where T : class + { + return new EntityFrameworkLive(item); + } + } +} diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs new file mode 100644 index 0000000000..29e5756dba --- /dev/null +++ b/osu.Game/Database/ILive.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Database +{ + /// + /// A wrapper to provide access to database backed classes in a thread-safe manner. + /// + /// The databased type. + public interface ILive where T : class + { + Guid ID { get; } + + /// + /// Perform a read operation on this live object. + /// + /// The action to perform. + void PerformRead(Action perform); + + /// + /// Perform a read operation on this live object. + /// + /// The action to perform. + TReturn PerformRead(Func perform); + + /// + /// Perform a write operation on this live object. + /// + /// The action to perform. + void PerformWrite(Action perform); + + /// + /// Resolve the value of this instance on the current thread's context. + /// + /// + /// After resolving the data should not be passed between threads. + /// + T Value { get; } + } +} diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index fa3b4d9152..e94af01772 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Database Task Import(params ImportTask[] tasks); - Task> Import(ProgressNotification notification, params ImportTask[] tasks); + Task>> Import(ProgressNotification notification, params ImportTask[] tasks); /// /// Import one from the filesystem and delete the file on success. @@ -38,7 +38,7 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from an . @@ -46,7 +46,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); + Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from a . @@ -55,7 +55,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Database/IPresentImports.cs b/osu.Game/Database/IPresentImports.cs index 39b495ebd5..6aa29a5083 100644 --- a/osu.Game/Database/IPresentImports.cs +++ b/osu.Game/Database/IPresentImports.cs @@ -12,6 +12,6 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action> PresentImport { set; } + public Action>> PresentImport { set; } } } diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml new file mode 100644 index 0000000000..cc07b89533 --- /dev/null +++ b/osu.Game/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 99925bb1fb..35ec213755 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -624,10 +624,10 @@ namespace osu.Game SkinManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n); - BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); + BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); ScoreManager.PostNotification = n => Notifications.Post(n); - ScoreManager.PresentImport = items => PresentScore(items.First()); + ScoreManager.PresentImport = items => PresentScore(items.First().Value); // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d83b4e3f1d..aa0ee4bbbb 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -299,22 +299,22 @@ namespace osu.Game.Scoring public IEnumerable HandledExtensions => scoreModelManager.HandledExtensions; - public Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return scoreModelManager.Import(notification, tasks); } - public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(task, lowPriority, cancellationToken); } - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public Task Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -365,7 +365,7 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action> PresentImport + public Action>> PresentImport { set => scoreModelManager.PresentImport = value; } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index cfe14eab92..fbd33cad67 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -101,8 +101,12 @@ namespace osu.Game.Screens.Menu // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; - import.Protected = true; - beatmaps.Update(import); + + import.PerformWrite(b => + { + b.Protected = true; + beatmaps.Update(b); + }); loadThemedIntro(); } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index edeb17cbad..3842acab74 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -207,7 +207,7 @@ namespace osu.Game.Skinning Name = skin.SkinInfo.Name + " (modified)", Creator = skin.SkinInfo.Creator, InstantiationInfo = skin.SkinInfo.InstantiationInfo, - }).Result; + }).Result.Value; } public void Save(Skin skin)