mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +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) =>
|
||||
{
|
||||
using (var importer = new BeatmapImporter(storage, realm))
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
@ -76,7 +77,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using (var importer = new BeatmapImporter(storage, realm))
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
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);
|
||||
|
||||
using (var importer = new BeatmapImporter(storage, realm))
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
@ -160,7 +163,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using (var importer = new BeatmapImporter(storage, realm))
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
|
||||
using (new RealmRulesetStore(realm, storage))
|
||||
{
|
||||
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
@ -187,7 +191,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -199,7 +203,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -217,7 +221,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -231,7 +235,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? tempPath = TestResources.GetTestBeatmapForImport();
|
||||
@ -261,7 +265,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -281,7 +285,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -317,7 +321,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -366,7 +370,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -417,7 +421,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -465,7 +469,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -513,7 +517,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -548,7 +552,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var progressNotification = new ImportProgressNotification();
|
||||
@ -586,7 +590,7 @@ namespace osu.Game.Tests.Database
|
||||
Interlocked.Increment(ref loggedExceptionCount);
|
||||
};
|
||||
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -644,7 +648,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm, batchImport: true);
|
||||
@ -671,7 +675,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realmFactory);
|
||||
var importer = new BeatmapImporter(storage, realmFactory);
|
||||
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||
@ -703,7 +707,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -730,7 +734,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
@ -756,7 +760,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
@ -804,7 +808,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -821,7 +825,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -857,7 +861,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -899,7 +903,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
string? temp = TestResources.GetTestBeatmapForImport();
|
||||
@ -950,7 +954,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
RunTestWithRealmAsync(async (realm, storage) =>
|
||||
{
|
||||
using var importer = new BeatmapImporter(storage, realm);
|
||||
var importer = new BeatmapImporter(storage, realm);
|
||||
using var store = new RealmRulesetStore(realm, storage);
|
||||
|
||||
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
|
||||
{
|
||||
private readonly TestBeatmapManager testBeatmapManager;
|
||||
|
||||
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
|
||||
: base(storage, databaseAccess, beatmapOnlineLookupQueue)
|
||||
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapUpdater beatmapUpdater)
|
||||
: base(storage, databaseAccess, beatmapUpdater)
|
||||
{
|
||||
this.testBeatmapManager = testBeatmapManager;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Screens.Edit;
|
||||
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));
|
||||
}
|
||||
|
||||
[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]
|
||||
public void TestExitWithoutSaveFromExistingBeatmap()
|
||||
{
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -166,15 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(rulesets.Length, rulesets);
|
||||
|
||||
for (int i = 0; i < rulesets.Length; i++)
|
||||
var importedBeatmapSet = Game.BeatmapManager.Import(beatmapSet);
|
||||
|
||||
Debug.Assert(importedBeatmapSet != null);
|
||||
|
||||
importedBeatmapSet.PerformWrite(s =>
|
||||
{
|
||||
var beatmap = beatmapSet.Beatmaps[i];
|
||||
for (int i = 0; i < rulesets.Length; i++)
|
||||
{
|
||||
var beatmap = s.Beatmaps[i];
|
||||
|
||||
beatmap.StarRating = i + 1;
|
||||
beatmap.DifficultyName = $"SR{i + 1}";
|
||||
}
|
||||
beatmap.StarRating = 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);
|
||||
|
@ -81,6 +81,11 @@ namespace osu.Game.Beatmaps
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Invalidate(IBeatmapInfo beatmap)
|
||||
{
|
||||
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a bindable containing the star difficulty of a <see cref="BeatmapInfo"/> that follows the currently-selected ruleset and mods.
|
||||
/// </summary>
|
||||
|
@ -6,10 +6,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
@ -19,8 +17,6 @@ using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
@ -29,18 +25,18 @@ namespace osu.Game.Beatmaps
|
||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||
/// </summary>
|
||||
[ExcludeFromDynamicCompile]
|
||||
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>, IDisposable
|
||||
public class BeatmapImporter : RealmArchiveModelImporter<BeatmapSetInfo>
|
||||
{
|
||||
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
|
||||
|
||||
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)
|
||||
{
|
||||
this.onlineLookupQueue = onlineLookupQueue;
|
||||
this.beatmapUpdater = beatmapUpdater;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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.
|
||||
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)
|
||||
{
|
||||
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(),
|
||||
};
|
||||
|
||||
updateBeatmapStatistics(beatmap, decoded);
|
||||
|
||||
beatmaps.Add(beatmap);
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
WorkingBeatmap? defaultBeatmap = null, bool performOnlineLookups = false)
|
||||
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
|
||||
: base(storage, realm)
|
||||
{
|
||||
if (performOnlineLookups)
|
||||
@ -52,14 +52,17 @@ namespace osu.Game.Beatmaps
|
||||
if (api == null)
|
||||
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;
|
||||
|
||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||
|
||||
beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue);
|
||||
beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, beatmapUpdater);
|
||||
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
|
||||
|
||||
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
|
||||
@ -71,8 +74,8 @@ namespace osu.Game.Beatmaps
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
}
|
||||
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue? onlineLookupQueue) =>
|
||||
new BeatmapImporter(storage, realm, onlineLookupQueue);
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapUpdater? beatmapUpdater) =>
|
||||
new BeatmapImporter(storage, realm, beatmapUpdater);
|
||||
|
||||
/// <summary>
|
||||
/// 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));
|
||||
|
||||
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)
|
||||
{
|
||||
@ -418,29 +428,42 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
#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)
|
||||
{
|
||||
// Detached sets don't come with files.
|
||||
// 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 (beatmapInfo != null)
|
||||
{
|
||||
Realm.Run(r =>
|
||||
// Detached sets don't come with files.
|
||||
// If we seem to be missing files, now is a good time to re-fetch.
|
||||
if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0)
|
||||
{
|
||||
var refetch = r.Find<BeatmapInfo>(beatmapInfo.ID)?.Detach();
|
||||
workingBeatmapCache.Invalidate(beatmapInfo);
|
||||
|
||||
if (refetch != null)
|
||||
beatmapInfo = refetch;
|
||||
});
|
||||
Guid id = beatmapInfo.ID;
|
||||
beatmapInfo = Realm.Run(r => r.Find<BeatmapInfo>(id)?.Detach()) ?? beatmapInfo;
|
||||
}
|
||||
|
||||
Debug.Assert(beatmapInfo.IsManaged != true);
|
||||
}
|
||||
|
||||
Debug.Assert(beatmapInfo?.IsManaged != true);
|
||||
|
||||
return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
|
||||
}
|
||||
|
||||
WorkingBeatmap IWorkingBeatmapCache.GetWorkingBeatmap(BeatmapInfo beatmapInfo) => GetWorkingBeatmap(beatmapInfo);
|
||||
void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo);
|
||||
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));
|
||||
|
||||
#endregion
|
||||
@ -449,7 +472,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
onlineBeatmapLookupQueue?.Dispose();
|
||||
beatmapUpdater?.Dispose();
|
||||
}
|
||||
|
||||
#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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public interface IWorkingBeatmapCache
|
||||
|
@ -76,10 +76,13 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
Logger.Log($"Invalidating working beatmap cache for {info}");
|
||||
workingCache.Remove(working);
|
||||
OnInvalidated?.Invoke(working);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<WorkingBeatmap> OnInvalidated;
|
||||
|
||||
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
if (beatmapInfo?.BeatmapSet == null)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -39,6 +40,19 @@ namespace osu.Game.Database
|
||||
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) =>
|
||||
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()
|
||||
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(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.
|
||||
// This is required as the editor makes its local changes via EditorBeatmap
|
||||
// (which are not propagated outwards to a potentially cached WorkingBeatmap).
|
||||
((IWorkingBeatmapCache)beatmapManager).Invalidate(Beatmap.Value.BeatmapInfo);
|
||||
var refetchedBeatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == Beatmap.Value.BeatmapInfo.ID);
|
||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(refetchedBeatmapInfo);
|
||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo, true);
|
||||
|
||||
if (!(refetchedBeatmap is DummyWorkingBeatmap))
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user