mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 18:03:11 +08:00
Merge pull request #18835 from peppy/beatmap-update-flow
Split out beatmap update tasks to `BeatmapUpdater` and invoke from editor save flow
This commit is contained in:
commit
3b1842a2c2
@ -35,7 +35,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -76,7 +77,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -134,7 +136,8 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var manager = new ModelManager<BeatmapSetInfo>(storage, realm);
|
var manager = new ModelManager<BeatmapSetInfo>(storage, realm);
|
||||||
|
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
@ -160,7 +163,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using (var importer = new BeatmapImporter(storage, realm))
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
|
|
||||||
using (new RealmRulesetStore(realm, storage))
|
using (new RealmRulesetStore(realm, storage))
|
||||||
{
|
{
|
||||||
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||||
@ -187,7 +191,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
await LoadOszIntoStore(importer, realm.Realm);
|
await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -199,7 +203,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -217,7 +221,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -231,7 +235,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? tempPath = TestResources.GetTestBeatmapForImport();
|
string? tempPath = TestResources.GetTestBeatmapForImport();
|
||||||
@ -261,7 +265,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -281,7 +285,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -317,7 +321,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -366,7 +370,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -417,7 +421,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -465,7 +469,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -513,7 +517,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -548,7 +552,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var progressNotification = new ImportProgressNotification();
|
var progressNotification = new ImportProgressNotification();
|
||||||
@ -586,7 +590,7 @@ namespace osu.Game.Tests.Database
|
|||||||
Interlocked.Increment(ref loggedExceptionCount);
|
Interlocked.Increment(ref loggedExceptionCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -644,7 +648,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true);
|
var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true);
|
||||||
@ -671,7 +675,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realmFactory);
|
var importer = new BeatmapImporter(storage, realmFactory);
|
||||||
using var store = new RealmRulesetStore(realmFactory, storage);
|
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||||
@ -703,7 +707,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -730,7 +734,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
@ -756,7 +760,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realm, storage) =>
|
RunTestWithRealm((realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
var metadata = new BeatmapMetadata
|
var metadata = new BeatmapMetadata
|
||||||
@ -804,7 +808,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -821,7 +825,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -857,7 +861,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -899,7 +903,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
@ -950,7 +954,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realm, storage) =>
|
RunTestWithRealmAsync(async (realm, storage) =>
|
||||||
{
|
{
|
||||||
using var importer = new BeatmapImporter(storage, realm);
|
var importer = new BeatmapImporter(storage, realm);
|
||||||
using var store = new RealmRulesetStore(realm, storage);
|
using var store = new RealmRulesetStore(realm, storage);
|
||||||
|
|
||||||
string? temp = TestResources.GetTestBeatmapForImport();
|
string? temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
@ -212,17 +212,17 @@ namespace osu.Game.Tests.Online
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
|
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater beatmapUpdater)
|
||||||
{
|
{
|
||||||
return new TestBeatmapImporter(this, storage, realm, onlineLookupQueue);
|
return new TestBeatmapImporter(this, storage, realm, beatmapUpdater);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TestBeatmapImporter : BeatmapImporter
|
internal class TestBeatmapImporter : BeatmapImporter
|
||||||
{
|
{
|
||||||
private readonly TestBeatmapManager testBeatmapManager;
|
private readonly TestBeatmapManager testBeatmapManager;
|
||||||
|
|
||||||
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater)
|
||||||
: base(storage, databaseAccess, beatmapOnlineLookupQueue)
|
: base(storage, databaseAccess, beatmapUpdater)
|
||||||
{
|
{
|
||||||
this.testBeatmapManager = testBeatmapManager;
|
this.testBeatmapManager = testBeatmapManager;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
@ -130,6 +131,54 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
!ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLengthAndStarRatingUpdated()
|
||||||
|
{
|
||||||
|
WorkingBeatmap working = null;
|
||||||
|
double lastStarRating = 0;
|
||||||
|
double lastLength = 0;
|
||||||
|
|
||||||
|
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
|
||||||
|
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("One hitobject placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true));
|
||||||
|
|
||||||
|
AddAssert("Beatmap length is zero", () => working.BeatmapInfo.Length == 0);
|
||||||
|
checkDifficultyIncreased();
|
||||||
|
|
||||||
|
AddStep("Move forward", () => InputManager.Key(Key.Right));
|
||||||
|
AddStep("Place another hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Two hitobjects placed", () => EditorBeatmap.HitObjects.Count == 2);
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
AddStep("Get working beatmap", () => working = Game.BeatmapManager.GetWorkingBeatmap(EditorBeatmap.BeatmapInfo, true));
|
||||||
|
|
||||||
|
checkDifficultyIncreased();
|
||||||
|
checkLengthIncreased();
|
||||||
|
|
||||||
|
void checkLengthIncreased()
|
||||||
|
{
|
||||||
|
AddStep("Beatmap length increased", () =>
|
||||||
|
{
|
||||||
|
Assert.That(working.BeatmapInfo.Length, Is.GreaterThan(lastLength));
|
||||||
|
lastLength = working.BeatmapInfo.Length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkDifficultyIncreased()
|
||||||
|
{
|
||||||
|
AddStep("Beatmap difficulty increased", () =>
|
||||||
|
{
|
||||||
|
Assert.That(working.BeatmapInfo.StarRating, Is.GreaterThan(lastStarRating));
|
||||||
|
lastStarRating = working.BeatmapInfo.StarRating;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExitWithoutSaveFromExistingBeatmap()
|
public void TestExitWithoutSaveFromExistingBeatmap()
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -166,15 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(rulesets.Length, rulesets);
|
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(rulesets.Length, rulesets);
|
||||||
|
|
||||||
|
var importedBeatmapSet = Game.BeatmapManager.Import(beatmapSet);
|
||||||
|
|
||||||
|
Debug.Assert(importedBeatmapSet != null);
|
||||||
|
|
||||||
|
importedBeatmapSet.PerformWrite(s =>
|
||||||
|
{
|
||||||
for (int i = 0; i < rulesets.Length; i++)
|
for (int i = 0; i < rulesets.Length; i++)
|
||||||
{
|
{
|
||||||
var beatmap = beatmapSet.Beatmaps[i];
|
var beatmap = s.Beatmaps[i];
|
||||||
|
|
||||||
beatmap.StarRating = i + 1;
|
beatmap.StarRating = i + 1;
|
||||||
beatmap.DifficultyName = $"SR{i + 1}";
|
beatmap.DifficultyName = $"SR{i + 1}";
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Game.BeatmapManager.Import(beatmapSet)?.Value;
|
return importedBeatmapSet.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);
|
private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);
|
||||||
|
@ -81,6 +81,11 @@ namespace osu.Game.Beatmaps
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Invalidate(IBeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> that follows the currently-selected ruleset and mods.
|
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> that follows the currently-selected ruleset and mods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -6,10 +6,8 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -19,8 +17,6 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -29,18 +25,18 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>, IDisposable
|
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>
|
||||||
{
|
{
|
||||||
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
|
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
|
||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osu" };
|
protected override string[] HashableFileTypes => new[] { ".osu" };
|
||||||
|
|
||||||
private readonly BeatmapOnlineLookupQueue? onlineLookupQueue;
|
private readonly BeatmapUpdater? beatmapUpdater;
|
||||||
|
|
||||||
public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
|
public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapUpdater? beatmapUpdater = null)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
this.onlineLookupQueue = onlineLookupQueue;
|
this.beatmapUpdater = beatmapUpdater;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz";
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz";
|
||||||
@ -64,8 +60,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0);
|
bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0);
|
||||||
|
|
||||||
onlineLookupQueue?.Update(beatmapSet);
|
// TODO: this may no longer be valid as we aren't doing an online population at this point.
|
||||||
|
|
||||||
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
||||||
if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0))
|
if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0))
|
||||||
{
|
{
|
||||||
@ -101,6 +96,13 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void PostImport(BeatmapSetInfo model, Realm realm)
|
||||||
|
{
|
||||||
|
base.PostImport(model, realm);
|
||||||
|
|
||||||
|
beatmapUpdater?.Process(model);
|
||||||
|
}
|
||||||
|
|
||||||
private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm)
|
private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm)
|
||||||
{
|
{
|
||||||
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID > 0).Select(b => b.OnlineID).ToList();
|
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID > 0).Select(b => b.OnlineID).ToList();
|
||||||
@ -286,64 +288,11 @@ namespace osu.Game.Beatmaps
|
|||||||
MD5Hash = memoryStream.ComputeMD5Hash(),
|
MD5Hash = memoryStream.ComputeMD5Hash(),
|
||||||
};
|
};
|
||||||
|
|
||||||
updateBeatmapStatistics(beatmap, decoded);
|
|
||||||
|
|
||||||
beatmaps.Add(beatmap);
|
beatmaps.Add(beatmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return beatmaps;
|
return beatmaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBeatmapStatistics(BeatmapInfo beatmap, IBeatmap decoded)
|
|
||||||
{
|
|
||||||
var rulesetInstance = ((IRulesetInfo)beatmap.Ruleset).CreateInstance();
|
|
||||||
|
|
||||||
decoded.BeatmapInfo.Ruleset = rulesetInstance.RulesetInfo;
|
|
||||||
|
|
||||||
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
|
||||||
beatmap.StarRating = rulesetInstance.CreateDifficultyCalculator(new DummyConversionBeatmap(decoded)).Calculate().StarRating;
|
|
||||||
beatmap.Length = calculateLength(decoded);
|
|
||||||
beatmap.BPM = 60000 / decoded.GetMostCommonBeatLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
private double calculateLength(IBeatmap b)
|
|
||||||
{
|
|
||||||
if (!b.HitObjects.Any())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var lastObject = b.HitObjects.Last();
|
|
||||||
|
|
||||||
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
|
|
||||||
double endTime = lastObject.GetEndTime();
|
|
||||||
double startTime = b.HitObjects.First().StartTime;
|
|
||||||
|
|
||||||
return endTime - startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
onlineLookupQueue?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
|
||||||
/// </summary>
|
|
||||||
private class DummyConversionBeatmap : WorkingBeatmap
|
|
||||||
{
|
|
||||||
private readonly IBeatmap beatmap;
|
|
||||||
|
|
||||||
public DummyConversionBeatmap(IBeatmap beatmap)
|
|
||||||
: base(beatmap.BeatmapInfo, null)
|
|
||||||
{
|
|
||||||
this.beatmap = beatmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
|
||||||
protected override Texture? GetBackground() => null;
|
|
||||||
protected override Track? GetBeatmapTrack() => null;
|
|
||||||
protected internal override ISkin? GetSkin() => null;
|
|
||||||
public override Stream? GetStream(string storagePath) => null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,10 @@ namespace osu.Game.Beatmaps
|
|||||||
private readonly BeatmapImporter beatmapImporter;
|
private readonly BeatmapImporter beatmapImporter;
|
||||||
|
|
||||||
private readonly WorkingBeatmapCache workingBeatmapCache;
|
private readonly WorkingBeatmapCache workingBeatmapCache;
|
||||||
private readonly BeatmapOnlineLookupQueue? onlineBeatmapLookupQueue;
|
private readonly BeatmapUpdater? beatmapUpdater;
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
|
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
|
||||||
WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
|
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
if (performOnlineLookups)
|
if (performOnlineLookups)
|
||||||
@ -52,14 +52,17 @@ namespace osu.Game.Beatmaps
|
|||||||
if (api == null)
|
if (api == null)
|
||||||
throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required.");
|
throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required.");
|
||||||
|
|
||||||
onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
if (difficultyCache == null)
|
||||||
|
throw new ArgumentNullException(nameof(difficultyCache), "Difficulty cache must be provided if online lookups are required.");
|
||||||
|
|
||||||
|
beatmapUpdater = new BeatmapUpdater(this, difficultyCache, api, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userResources = new RealmFileStore(realm, storage).Store;
|
var userResources = new RealmFileStore(realm, storage).Store;
|
||||||
|
|
||||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||||
|
|
||||||
beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue);
|
beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater);
|
||||||
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
|
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
|
||||||
|
|
||||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
||||||
@ -71,8 +74,8 @@ namespace osu.Game.Beatmaps
|
|||||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
|
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) =>
|
||||||
new BeatmapImporter(storage, realm, onlineLookupQueue);
|
new BeatmapImporter(storage, realm, beatmapUpdater);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
|
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
|
||||||
@ -314,10 +317,17 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo));
|
||||||
|
|
||||||
Realm.Write(r => setInfo.CopyChangesToRealm(r.Find<BeatmapSetInfo>(setInfo.ID)));
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
var liveBeatmapSet = r.Find<BeatmapSetInfo>(setInfo.ID);
|
||||||
|
|
||||||
|
setInfo.CopyChangesToRealm(liveBeatmapSet);
|
||||||
|
|
||||||
|
beatmapUpdater?.Process(liveBeatmapSet, r);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
Debug.Assert(beatmapInfo.BeatmapSet != null);
|
||||||
|
|
||||||
static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo)
|
static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -418,29 +428,42 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
#region Implementation of IWorkingBeatmapCache
|
#region Implementation of IWorkingBeatmapCache
|
||||||
|
|
||||||
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo)
|
/// <summary>
|
||||||
|
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmapInfo">The beatmap to lookup.</param>
|
||||||
|
/// <param name="refetch">Whether to force a refetch from the database to ensure <see cref="BeatmapInfo"/> is up-to-date.</param>
|
||||||
|
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
|
||||||
|
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo, bool refetch = false)
|
||||||
|
{
|
||||||
|
if (beatmapInfo != null)
|
||||||
{
|
{
|
||||||
// Detached sets don't come with files.
|
// Detached sets don't come with files.
|
||||||
// If we seem to be missing files, now is a good time to re-fetch.
|
// If we seem to be missing files, now is a good time to re-fetch.
|
||||||
if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0)
|
if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0)
|
||||||
{
|
{
|
||||||
Realm.Run(r =>
|
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||||
{
|
|
||||||
var refetch = r.Find<BeatmapInfo>(beatmapInfo.ID)?.Detach();
|
|
||||||
|
|
||||||
if (refetch != null)
|
Guid id = beatmapInfo.ID;
|
||||||
beatmapInfo = refetch;
|
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(beatmapInfo?.IsManaged != true);
|
Debug.Assert(beatmapInfo.IsManaged != true);
|
||||||
|
}
|
||||||
|
|
||||||
return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
|
return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorkingBeatmap IWorkingBeatmapCache.GetWorkingBeatmap(BeatmapInfo beatmapInfo) => GetWorkingBeatmap(beatmapInfo);
|
||||||
void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo);
|
void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo);
|
||||||
void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo);
|
void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo);
|
||||||
|
|
||||||
|
public event Action<WorkingBeatmap>? OnInvalidated
|
||||||
|
{
|
||||||
|
add => workingBeatmapCache.OnInvalidated += value;
|
||||||
|
remove => workingBeatmapCache.OnInvalidated -= value;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -449,7 +472,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
onlineBeatmapLookupQueue?.Dispose();
|
beatmapUpdater?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
100
osu.Game/Beatmaps/BeatmapUpdater.cs
Normal file
100
osu.Game/Beatmaps/BeatmapUpdater.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 System.Threading.Tasks;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using Realms;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles all processing required to ensure a local beatmap is in a consistent state with any changes.
|
||||||
|
/// </summary>
|
||||||
|
public class BeatmapUpdater : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IWorkingBeatmapCache workingBeatmapCache;
|
||||||
|
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||||
|
private readonly BeatmapDifficultyCache difficultyCache;
|
||||||
|
|
||||||
|
public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapDifficultyCache difficultyCache, IAPIProvider api, Storage storage)
|
||||||
|
{
|
||||||
|
this.workingBeatmapCache = workingBeatmapCache;
|
||||||
|
this.difficultyCache = difficultyCache;
|
||||||
|
|
||||||
|
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a beatmap for background processing.
|
||||||
|
/// </summary>
|
||||||
|
public void Queue(Live<BeatmapSetInfo> beatmap)
|
||||||
|
{
|
||||||
|
// For now, just fire off a task.
|
||||||
|
// TODO: Add actual queueing probably.
|
||||||
|
Task.Factory.StartNew(() => beatmap.PerformRead(Process));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run all processing on a beatmap immediately.
|
||||||
|
/// </summary>
|
||||||
|
public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => Process(beatmapSet, r));
|
||||||
|
|
||||||
|
public void Process(BeatmapSetInfo beatmapSet, Realm realm)
|
||||||
|
{
|
||||||
|
// Before we use below, we want to invalidate.
|
||||||
|
workingBeatmapCache.Invalidate(beatmapSet);
|
||||||
|
|
||||||
|
onlineLookupQueue.Update(beatmapSet);
|
||||||
|
|
||||||
|
foreach (var beatmap in beatmapSet.Beatmaps)
|
||||||
|
{
|
||||||
|
difficultyCache.Invalidate(beatmap);
|
||||||
|
|
||||||
|
var working = workingBeatmapCache.GetWorkingBeatmap(beatmap);
|
||||||
|
var ruleset = working.BeatmapInfo.Ruleset.CreateInstance();
|
||||||
|
|
||||||
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
|
var calculator = ruleset.CreateDifficultyCalculator(working);
|
||||||
|
|
||||||
|
beatmap.StarRating = calculator.Calculate().StarRating;
|
||||||
|
beatmap.Length = calculateLength(working.Beatmap);
|
||||||
|
beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required.
|
||||||
|
workingBeatmapCache.Invalidate(beatmapSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calculateLength(IBeatmap b)
|
||||||
|
{
|
||||||
|
if (!b.HitObjects.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var lastObject = b.HitObjects.Last();
|
||||||
|
|
||||||
|
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
|
||||||
|
double endTime = lastObject.GetEndTime();
|
||||||
|
double startTime = b.HitObjects.First().StartTime;
|
||||||
|
|
||||||
|
return endTime - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (onlineLookupQueue.IsNotNull())
|
||||||
|
onlineLookupQueue.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public interface IWorkingBeatmapCache
|
public interface IWorkingBeatmapCache
|
||||||
|
@ -76,10 +76,13 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
Logger.Log($"Invalidating working beatmap cache for {info}");
|
Logger.Log($"Invalidating working beatmap cache for {info}");
|
||||||
workingCache.Remove(working);
|
workingCache.Remove(working);
|
||||||
|
OnInvalidated?.Invoke(working);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Action<WorkingBeatmap> OnInvalidated;
|
||||||
|
|
||||||
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
if (beatmapInfo?.BeatmapSet == null)
|
if (beatmapInfo?.BeatmapSet == null)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -39,6 +40,19 @@ namespace osu.Game.Database
|
|||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalidate all entries matching a provided predicate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchKeyPredicate">The predicate to decide which keys should be invalidated.</param>
|
||||||
|
protected void Invalidate(Func<TLookup, bool> matchKeyPredicate)
|
||||||
|
{
|
||||||
|
foreach (var kvp in cache)
|
||||||
|
{
|
||||||
|
if (matchKeyPredicate(kvp.Key))
|
||||||
|
cache.TryRemove(kvp.Key, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
|
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
|
||||||
cache.TryGetValue(lookup, out value);
|
cache.TryGetValue(lookup, out value);
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// 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, difficultyCache, LocalConfig));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
|
||||||
|
|
||||||
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
|
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
|
||||||
dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API));
|
dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API));
|
||||||
|
@ -657,9 +657,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
// To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend.
|
// To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend.
|
||||||
// This is required as the editor makes its local changes via EditorBeatmap
|
// This is required as the editor makes its local changes via EditorBeatmap
|
||||||
// (which are not propagated outwards to a potentially cached WorkingBeatmap).
|
// (which are not propagated outwards to a potentially cached WorkingBeatmap).
|
||||||
((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo);
|
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo, true);
|
||||||
var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID);
|
|
||||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo);
|
|
||||||
|
|
||||||
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user