Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn difficulty & performance attributes into structs #30727

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public EmptyFreeformDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap b
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
return new DifficultyAttributes(mods, 0);
return new DifficultyAttributes();
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatma
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
return new DifficultyAttributes(mods, 0);
return new DifficultyAttributes();
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public EmptyScrollingDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
return new DifficultyAttributes(mods, 0);
return new DifficultyAttributes();
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public PippidonDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatma
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
return new DifficultyAttributes(mods, 0);
return new DifficultyAttributes();
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
Expand Down
96 changes: 96 additions & 0 deletions osu.Game.Benchmarks/BenchmarkDifficultyCalculation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.IO;
using BenchmarkDotNet.Attributes;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Scoring;
using osu.Game.Tests.Resources;

namespace osu.Game.Benchmarks
{
public class BenchmarkDifficultyCalculation : BenchmarkTest
{
private FlatWorkingBeatmap beatmap = null!;

private IDifficultyAttributes osuAttributes = null!;
private IDifficultyAttributes taikoAttributes = null!;
private IDifficultyAttributes catchAttributes = null!;
private IDifficultyAttributes maniaAttributes = null!;

public override void SetUp()
{
using var resources = new DllResourceStore(typeof(TestResources).Assembly);
using var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz");
using var zipReader = new ZipArchiveReader(archive);

using var beatmapStream = new MemoryStream();
zipReader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
beatmapStream.Seek(0, SeekOrigin.Begin);
var reader = new LineBufferedReader(beatmapStream);
var decoder = Decoder.GetDecoder<Beatmap>(reader);

beatmap = new FlatWorkingBeatmap(decoder.Decode(reader));

// Prepare difficulty attributes for an isolated performance calculation in every mode.
osuAttributes = DifficultyOsu();
taikoAttributes = DifficultyTaiko();
catchAttributes = DifficultyCatch();
maniaAttributes = DifficultyMania();
}

[Benchmark]
public IDifficultyAttributes DifficultyOsu() => new OsuRuleset().CreateDifficultyCalculator(beatmap).Calculate();

[Benchmark]
public IDifficultyAttributes DifficultyTaiko() => new TaikoRuleset().CreateDifficultyCalculator(beatmap).Calculate();

[Benchmark]
public IDifficultyAttributes DifficultyCatch() => new CatchRuleset().CreateDifficultyCalculator(beatmap).Calculate();

[Benchmark]
public IDifficultyAttributes DifficultyMania() => new ManiaRuleset().CreateDifficultyCalculator(beatmap).Calculate();

[Benchmark]
public void PerformanceOsu()
{
Ruleset ruleset = new OsuRuleset();
ScoreInfo score = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo);
ruleset.CreatePerformanceCalculator()!.Calculate(score, osuAttributes);
}

[Benchmark]
public void PerformanceTaiko()
{
Ruleset ruleset = new TaikoRuleset();
ScoreInfo score = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo);
ruleset.CreatePerformanceCalculator()!.Calculate(score, taikoAttributes);
}

[Benchmark]
public void PerformanceCatch()
{
Ruleset ruleset = new CatchRuleset();
ScoreInfo score = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo);
ruleset.CreatePerformanceCalculator()!.Calculate(score, catchAttributes);
}

[Benchmark]
public void PerformanceMania()
{
Ruleset ruleset = new ManiaRuleset();
ScoreInfo score = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo);
ruleset.CreatePerformanceCalculator()!.Calculate(score, maniaAttributes);
}
}
}
28 changes: 16 additions & 12 deletions osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@

namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
[JsonObject(MemberSerialization.OptIn)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's that? why is it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute was previously defined on the class all attributes inherit from. Even though technically only the osu difficulty attributes need it right now, I decided to stick to how it was before.

public struct CatchDifficultyAttributes : IDifficultyAttributes
{
/// <inheritdoc/>
public double StarRating { get; set; }

/// <inheritdoc/>
public int MaxCombo { get; set; }

/// <summary>
/// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
Expand All @@ -19,22 +26,19 @@ public class CatchDifficultyAttributes : DifficultyAttributes
[JsonProperty("approach_rate")]
public double ApproachRate { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
public IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (IDifficultyAttributes.ATTRIB_ID_MAX_COMBO, MaxCombo);
// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (IDifficultyAttributes.ATTRIB_ID_AIM, StarRating);
yield return (IDifficultyAttributes.ATTRIB_ID_APPROACH_RATE, ApproachRate);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
public void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[IDifficultyAttributes.ATTRIB_ID_MAX_COMBO];
StarRating = values[IDifficultyAttributes.ATTRIB_ID_AIM];
ApproachRate = values[IDifficultyAttributes.ATTRIB_ID_APPROACH_RATE];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,17 @@ public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
{
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
return new CatchDifficultyAttributes { Mods = mods };
return new CatchDifficultyAttributes();

// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
{
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.GetMaxCombo(),
};
Expand Down
14 changes: 13 additions & 1 deletion osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty;

namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchPerformanceAttributes : PerformanceAttributes
public struct CatchPerformanceAttributes : IPerformanceAttributes
{
/// <summary>
/// Calculated score performance points.
/// </summary>
[JsonProperty("pp")]
public double Total { get; set; }

public IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
{
yield return new PerformanceDisplayAttribute(nameof(Total), "Achieved PP", Total);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public CatchPerformanceCalculator()
{
}

protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
protected override IPerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, IDifficultyAttributes attributes)
{
var catchAttributes = (CatchDifficultyAttributes)attributes;

Expand Down
28 changes: 16 additions & 12 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@

namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
[JsonObject(MemberSerialization.OptIn)]
public struct ManiaDifficultyAttributes : IDifficultyAttributes
{
/// <inheritdoc/>
public double StarRating { get; set; }

/// <inheritdoc/>
public int MaxCombo { get; set; }

/// <summary>
/// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
Expand All @@ -19,21 +26,18 @@ public class ManiaDifficultyAttributes : DifficultyAttributes
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
public IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (IDifficultyAttributes.ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (IDifficultyAttributes.ATTRIB_ID_DIFFICULTY, StarRating);
yield return (IDifficultyAttributes.ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
public void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
{
base.FromDatabaseAttributes(values, onlineInfo);

StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
MaxCombo = (int)values[IDifficultyAttributes.ATTRIB_ID_MAX_COMBO];
StarRating = values[IDifficultyAttributes.ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[IDifficultyAttributes.ATTRIB_ID_GREAT_HIT_WINDOW];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,17 @@ public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
protected override IDifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods };
return new ManiaDifficultyAttributes();

HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);

ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
{
StarRating = skills[0].DifficultyValue() * difficulty_multiplier,
Mods = mods,
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
Expand Down
14 changes: 9 additions & 5 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@

namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceAttributes : PerformanceAttributes
public struct ManiaPerformanceAttributes : IPerformanceAttributes
{
/// <summary>
/// Calculated score performance points.
/// </summary>
[JsonProperty("pp")]
public double Total { get; set; }

[JsonProperty("difficulty")]
public double Difficulty { get; set; }

public override IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
public IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
{
foreach (var attribute in base.GetAttributesForDisplay())
yield return attribute;

yield return new PerformanceDisplayAttribute(nameof(Total), "Achieved PP", Total);
yield return new PerformanceDisplayAttribute(nameof(Difficulty), "Difficulty", Difficulty);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public ManiaPerformanceCalculator()
{
}

protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, DifficultyAttributes attributes)
protected override IPerformanceAttributes CreatePerformanceAttributes(ScoreInfo score, IDifficultyAttributes attributes)
{
var maniaAttributes = (ManiaDifficultyAttributes)attributes;

Expand Down
12 changes: 12 additions & 0 deletions osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxComb
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());

[Test]
public void TestFlashlightDifficultyNullability()
{
IWorkingBeatmap beatmap = GetBeatmap("diffcalc-test");

OsuDifficultyAttributes attributes = (OsuDifficultyAttributes)CreateDifficultyCalculator(beatmap).Calculate();
Assert.IsNull(attributes.FlashlightDifficulty);

attributes = (OsuDifficultyAttributes)CreateDifficultyCalculator(GetBeatmap("diffcalc-test")).Calculate([new OsuModFlashlight()]);
Assert.IsNotNull(attributes.FlashlightDifficulty);
}

protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap);

protected override Ruleset CreateRuleset() => new OsuRuleset();
Expand Down
Loading
Loading