From afad2cf278cdbc142a8edd56e9b5a69f98cd3acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Mar 2025 11:52:09 +0100 Subject: [PATCH 1/3] Apply more granular copying from database when retrieving working beatmap --- osu.Game/Beatmaps/WorkingBeatmap.cs | 17 ++++++++++++----- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index fd40097c4e..8df57fd0c8 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -235,11 +235,18 @@ namespace osu.Game.Beatmaps // Todo: Handle cancellation during beatmap parsing var b = GetBeatmap() ?? new Beatmap(); - // The original beatmap version needs to be preserved as the database doesn't contain it - BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion; - - // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc) - b.BeatmapInfo = BeatmapInfo; + // Copy across values of key properties for which the database-backed model has data that the decoded beatmap isn't going to. + b.BeatmapInfo.ID = BeatmapInfo.ID; + b.BeatmapInfo.UserSettings = BeatmapInfo.UserSettings; + b.BeatmapInfo.BeatmapSet = BeatmapInfo.BeatmapSet; + b.BeatmapInfo.Status = BeatmapInfo.Status; + b.BeatmapInfo.OnlineID = BeatmapInfo.OnlineID; + b.BeatmapInfo.OnlineMD5Hash = BeatmapInfo.OnlineMD5Hash; + b.BeatmapInfo.LastLocalUpdate = BeatmapInfo.LastLocalUpdate; + b.BeatmapInfo.LastOnlineUpdate = BeatmapInfo.LastOnlineUpdate; + b.BeatmapInfo.LastPlayed = BeatmapInfo.LastPlayed; + b.BeatmapInfo.EditorTimestamp = BeatmapInfo.EditorTimestamp; + b.BeatmapInfo.StarRating = BeatmapInfo.StarRating; // this could be recomputed in the decoding process but it's a bit annoying to do. return b; }, loadCancellationSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 8af74d11d8..352012106a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -21,6 +21,7 @@ using osu.Framework.Statistics; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -152,14 +153,28 @@ namespace osu.Game.Beatmaps return null; } - if (stream.ComputeMD5Hash() != BeatmapInfo.MD5Hash) + string streamMD5 = stream.ComputeMD5Hash(); + string streamSHA2 = stream.ComputeSHA2Hash(); + + if (streamMD5 != BeatmapInfo.MD5Hash) { Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} does not have the expected hash).", level: LogLevel.Error); return null; } using (var reader = new LineBufferedReader(stream)) - return Decoder.GetDecoder(reader).Decode(reader); + { + var beatmap = Decoder.GetDecoder(reader).Decode(reader); + + beatmap.BeatmapInfo.MD5Hash = streamMD5; + beatmap.BeatmapInfo.Hash = streamSHA2; + beatmap.BeatmapInfo.Length = beatmap.CalculatePlayableLength(); + beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); + beatmap.BeatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); + beatmap.BeatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; + + return beatmap; + } } catch (Exception e) { From c962210b4f250fab62c31f082fedf34dbe26a8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 10 Mar 2025 13:21:27 +0100 Subject: [PATCH 2/3] Fix placement blueprint tests --- .../Editor/CatchPlacementBlueprintTestScene.cs | 2 ++ .../Editor/ManiaPlacementBlueprintTestScene.cs | 2 ++ .../Editor/TestSceneHitCirclePlacementBlueprint.cs | 1 + .../Editor/TestSceneSliderPlacementBlueprint.cs | 2 ++ .../Editor/TestSceneSpinnerPlacementBlueprint.cs | 2 ++ osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 4 +++- 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index a327e6d4c9..a5713feda3 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { public abstract partial class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene { + protected sealed override Ruleset CreateRuleset() => new CatchRuleset(); + protected const double TIME_SNAP = 100; protected DrawableCatchHitObject LastObject; diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 0f913a6a7d..83070c3e29 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { public abstract partial class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene { + protected sealed override Ruleset CreateRuleset() => new ManiaRuleset(); + private readonly Column column; [Cached(typeof(IReadOnlyList))] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs index a105d860bf..5bce97d7b8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { + protected sealed override Ruleset CreateRuleset() => new OsuRuleset(); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 5831cc0a8a..8835254c48 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { + protected sealed override Ruleset CreateRuleset() => new OsuRuleset(); + [SetUp] public void Setup() => Schedule(() => { diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs index d7b5cc73be..18834ef847 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { public partial class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene { + protected sealed override Ruleset CreateRuleset() => new OsuRuleset(); + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index baf614d1c8..a644936a16 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -51,7 +51,9 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap GetPlayableBeatmap() { - var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + var rulesetInfo = CreateRuleset()!.RulesetInfo; + var playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo); + playable.BeatmapInfo.Ruleset = rulesetInfo; playable.Difficulty.CircleSize = 2; return playable; } From be5c89c2e40321a1c10d80abb3e523686d7734f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 11 Mar 2025 17:03:06 +0900 Subject: [PATCH 3/3] Add basic helper method to update beatmap statistics --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 13 +++++++++++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 7 ++----- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 6 +----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 16b4b04ce4..25f98c812c 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -1,15 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Localisation; using osu.Game.Online.API; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Select; namespace osu.Game.Beatmaps { public static class BeatmapInfoExtensions { + /// + /// Given an , update length, BPM and object counts. + /// + public static void UpdateStatisticsFromBeatmap(this BeatmapInfo beatmapInfo, IBeatmap beatmap) + { + beatmapInfo.Length = beatmap.CalculatePlayableLength(); + beatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); + beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); + beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; + } + /// /// A user-presentable display title representing this beatmap. /// diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index efb432b84e..64ac69bb07 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -51,7 +51,7 @@ namespace osu.Game.Beatmaps if (lookupScope != MetadataLookupScope.None) metadataLookup.Update(beatmapSet, lookupScope == MetadataLookupScope.OnlineFirst); - foreach (var beatmap in beatmapSet.Beatmaps) + foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps) { difficultyCache.Invalidate(beatmap); @@ -63,10 +63,7 @@ namespace osu.Game.Beatmaps var calculator = ruleset.CreateDifficultyCalculator(working); beatmap.StarRating = calculator.Calculate().StarRating; - beatmap.Length = working.Beatmap.CalculatePlayableLength(); - beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength(); - beatmap.EndTimeObjectCount = working.Beatmap.HitObjects.Count(h => h is IHasDuration); - beatmap.TotalObjectCount = working.Beatmap.HitObjects.Count; + beatmap.UpdateStatisticsFromBeatmap(working.Beatmap); } // And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required. diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 352012106a..fdeb840977 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -21,7 +21,6 @@ using osu.Framework.Statistics; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.IO; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -168,10 +167,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.MD5Hash = streamMD5; beatmap.BeatmapInfo.Hash = streamSHA2; - beatmap.BeatmapInfo.Length = beatmap.CalculatePlayableLength(); - beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); - beatmap.BeatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration); - beatmap.BeatmapInfo.TotalObjectCount = beatmap.HitObjects.Count; + beatmap.BeatmapInfo.UpdateStatisticsFromBeatmap(beatmap); return beatmap; }