diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 88904bf85b..c17a9ddf5f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -18,6 +19,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; @@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.Navigation private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault(); [Test] - public void TestEditComponentDuringGameplay() + public void TestEditComponentFromGameplayScene() { advanceToSongSelect(); openSkinEditor(); @@ -69,6 +71,28 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); } + [Test] + public void TestMutateProtectedSkinDuringGameplay() + { + advanceToSongSelect(); + AddStep("set default skin", () => Game.Dependencies.Get().CurrentSkinInfo.SetDefault()); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("enable NF", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() }); + AddStep("enter gameplay", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => + { + DismissAnyNotifications(); + return Game.ScreenStack.CurrentScreen is Player; + }); + + openSkinEditor(); + AddUntilStep("current skin is mutable", () => !Game.Dependencies.Get().CurrentSkin.Value.SkinInfo.Value.Protected); + } + [Test] public void TestComponentsDeselectedOnSkinEditorHide() { diff --git a/osu.Game/Database/ImportParameters.cs b/osu.Game/Database/ImportParameters.cs index 83ca0ac694..8d37597afc 100644 --- a/osu.Game/Database/ImportParameters.cs +++ b/osu.Game/Database/ImportParameters.cs @@ -21,5 +21,11 @@ namespace osu.Game.Database /// Whether this import should use hard links rather than file copy operations if available. /// public bool PreferHardLinks { get; set; } + + /// + /// If set to , this import will not respect . + /// This is useful for cases where an import must complete even if gameplay is in progress. + /// + public bool ImportImmediately { get; set; } } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 730465e1b0..5383040eb4 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -261,7 +261,7 @@ namespace osu.Game.Database /// An optional cancellation token. public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm => { - pauseIfNecessary(cancellationToken); + pauseIfNecessary(parameters, cancellationToken); TModel? existing; @@ -560,9 +560,9 @@ namespace osu.Game.Database /// Whether to perform deletion. protected virtual bool ShouldDeleteArchive(string path) => false; - private void pauseIfNecessary(CancellationToken cancellationToken) + private void pauseIfNecessary(ImportParameters importParameters, CancellationToken cancellationToken) { - if (!PauseImports) + if (!PauseImports || importParameters.ImportImmediately) return; Logger.Log($@"{GetType().Name} is being paused."); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ca46d3af0c..59c2a8bca0 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -182,7 +182,10 @@ namespace osu.Game.Skinning Name = NamingUtils.GetNextBestName(existingSkinNames, $@"{s.Name} (modified)") }; - var result = skinImporter.ImportModel(skinInfo); + var result = skinImporter.ImportModel(skinInfo, parameters: new ImportParameters + { + ImportImmediately = true // to avoid possible deadlocks when editing skin during gameplay. + }); if (result != null) {