1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 14:37:30 +08:00

Decouple AdvancedStats from global mods

Closes https://github.com/ppy/osu/issues/30163.

If I'm to be blunt, the decoupled stuff in song select makes my head
spin. I spent a solid 20 minutes thinking how I was going to fix this
one but then finally realised that generally most of the cause there
was the fact that `AdvancedStats` was seeing the new rulesets *before*
the "ensure global selected mods are valid for current ruleset" logic,
and so decided to just _delay_ that until the decoupled transfer
thingamajig happens.

I was honestly considering combining `BeatmapInfo`, `Ruleset`, and
`Mods` into one property on `AdvancedStats`. I figured I'd rather not
push my luck and try the baseline version first, but I honestly think
that direction is going to be required at some point to properly corral
all of the decoupled madness taking place in song select.
This commit is contained in:
Bartłomiej Dach 2024-10-10 13:04:27 +02:00
parent a6f56036a2
commit f1842d781e
No known key found for this signature in database
3 changed files with 26 additions and 18 deletions

View File

@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select EZ mod", () => AddStep("select EZ mod", () =>
{ {
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() }; advancedStats.Mods.Value = new[] { ruleset.CreateMod<ModEasy>() };
}); });
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select HR mod", () => AddStep("select HR mod", () =>
{ {
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() }; advancedStats.Mods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
}); });
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue));
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>().AsNonNull();
difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty); difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty);
SelectedMods.Value = new[] { difficultyAdjustMod }; advancedStats.Mods.Value = new[] { difficultyAdjustMod };
}); });
AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue));
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f; difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;
difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f; difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f;
SelectedMods.Value = new[] { difficultyAdjustMod }; advancedStats.Mods.Value = new[] { difficultyAdjustMod };
}); });
AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue));

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using System;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -36,9 +37,6 @@ namespace osu.Game.Screens.Select.Details
[Resolved] [Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; } private BeatmapDifficultyCache difficultyCache { get; set; }
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty; private readonly StatisticRow starDifficulty;
@ -69,6 +67,14 @@ namespace osu.Game.Screens.Select.Details
/// </remarks> /// </remarks>
public Bindable<RulesetInfo> Ruleset { get; } = new Bindable<RulesetInfo>(); public Bindable<RulesetInfo> Ruleset { get; } = new Bindable<RulesetInfo>();
/// <summary>
/// Mods to be used for certain elements of display.
/// </summary>
/// <remarks>
/// No checks are done as to whether the mods specified are valid for the current <see cref="Ruleset"/>.
/// </remarks>
public Bindable<IReadOnlyList<Mod>> Mods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
public AdvancedStats(int columns = 1) public AdvancedStats(int columns = 1)
{ {
switch (columns) switch (columns)
@ -143,8 +149,7 @@ namespace osu.Game.Screens.Select.Details
base.LoadComplete(); base.LoadComplete();
Ruleset.BindValueChanged(_ => updateStatistics()); Ruleset.BindValueChanged(_ => updateStatistics());
Mods.BindValueChanged(modsChanged, true);
mods.BindValueChanged(modsChanged, true);
} }
private ModSettingChangeTracker modSettingChangeTracker; private ModSettingChangeTracker modSettingChangeTracker;
@ -173,14 +178,14 @@ namespace osu.Game.Screens.Select.Details
{ {
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(baseDifficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(originalDifficulty); mod.ApplyToDifficulty(originalDifficulty);
adjustedDifficulty = originalDifficulty; adjustedDifficulty = originalDifficulty;
if (Ruleset.Value != null) if (Ruleset.Value != null)
{ {
double rate = ModUtils.CalculateRateWithMods(mods.Value); double rate = ModUtils.CalculateRateWithMods(Mods.Value);
adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
@ -198,7 +203,7 @@ namespace osu.Game.Screens.Select.Details
// For the time being, the key count is static no matter what, because: // For the time being, the key count is static no matter what, because:
// a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering. // a) The method doesn't have knowledge of the active keymods. Doing so may require considerations for filtering.
// b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion. // b) Using the difficulty adjustment mod to adjust OD doesn't have an effect on conversion.
int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, mods.Value); int keyCount = baseDifficulty == null ? 0 : legacyRuleset.GetKeyCount(BeatmapInfo, Mods.Value);
FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania; FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania;
FirstValue.Value = (keyCount, keyCount); FirstValue.Value = (keyCount, keyCount);
@ -236,7 +241,7 @@ namespace osu.Game.Screens.Select.Details
starDifficultyCancellationSource = new CancellationTokenSource(); starDifficultyCancellationSource = new CancellationTokenSource();
var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, null, starDifficultyCancellationSource.Token); var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, null, starDifficultyCancellationSource.Token);
var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, Ruleset.Value, Mods.Value, starDifficultyCancellationSource.Token);
Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() =>
{ {

View File

@ -610,11 +610,6 @@ namespace osu.Game.Screens.Select
beatmapInfoPrevious = beatmap; beatmapInfoPrevious = beatmap;
} }
// we can't run this in the debounced run due to the selected mods bindable not being debounced,
// since mods could be updated to the new ruleset instances while the decoupled bindable is held behind,
// therefore resulting in performing difficulty calculation with invalid states.
advancedStats.Ruleset.Value = ruleset;
void run() void run()
{ {
// clear pending task immediately to track any potential nested debounce operation. // clear pending task immediately to track any potential nested debounce operation.
@ -878,6 +873,8 @@ namespace osu.Game.Screens.Select
ModSelect.Beatmap.Value = beatmap; ModSelect.Beatmap.Value = beatmap;
advancedStats.BeatmapInfo = beatmap.BeatmapInfo; advancedStats.BeatmapInfo = beatmap.BeatmapInfo;
advancedStats.Mods.Value = selectedMods.Value;
advancedStats.Ruleset.Value = Ruleset.Value;
bool beatmapSelected = beatmap is not DummyWorkingBeatmap; bool beatmapSelected = beatmap is not DummyWorkingBeatmap;
@ -990,6 +987,12 @@ namespace osu.Game.Screens.Select
Beatmap.BindValueChanged(updateCarouselSelection); Beatmap.BindValueChanged(updateCarouselSelection);
selectedMods.BindValueChanged(_ =>
{
if (decoupledRuleset.Value.Equals(rulesetNoDebounce))
advancedStats.Mods.Value = selectedMods.Value;
}, true);
boundLocalBindables = true; boundLocalBindables = true;
} }