diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index d7edb33c42..bf864f844c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -227,12 +227,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
- [Test]
- public void TestNoSubmissionOnCustomRuleset()
+ [TestCase(null)]
+ [TestCase(10)]
+ public void TestNoSubmissionOnCustomRuleset(int? rulesetId)
{
prepareTokenResponse(true);
- createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = 10 } });
+ createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = rulesetId } });
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index ac5b5d7a8a..3bcc00f5de 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps
#region Implementation of IHasOnlineID
- public int? OnlineID => OnlineBeatmapID;
+ public int OnlineID => OnlineBeatmapID ?? -1;
#endregion
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index 8b01831b3c..e8c77e792f 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Beatmaps
#region Implementation of IHasOnlineID
- public int? OnlineID => OnlineBeatmapSetID;
+ public int OnlineID => OnlineBeatmapSetID ?? -1;
#endregion
diff --git a/osu.Game/Database/IHasOnlineID.cs b/osu.Game/Database/IHasOnlineID.cs
index c55c461d2d..529c68a8f8 100644
--- a/osu.Game/Database/IHasOnlineID.cs
+++ b/osu.Game/Database/IHasOnlineID.cs
@@ -8,8 +8,8 @@ namespace osu.Game.Database
public interface IHasOnlineID
{
///
- /// The server-side ID representing this instance, if one exists.
+ /// The server-side ID representing this instance, if one exists. -1 denotes a missing ID.
///
- int? OnlineID { get; }
+ int OnlineID { get; }
}
}
diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs
index 82d51e365e..b5c44927ca 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmContextFactory.cs
@@ -2,12 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
+using osu.Game.Models;
using Realms;
#nullable enable
@@ -26,7 +28,12 @@ namespace osu.Game.Database
///
public readonly string Filename;
- private const int schema_version = 6;
+ ///
+ /// Version history:
+ /// 6 First tracked version (~20211018)
+ /// 7 Changed OnlineID fields to non-nullable to add indexing support (20211018)
+ ///
+ private const int schema_version = 7;
///
/// Lock object which is held during sections, blocking context creation during blocking periods.
@@ -120,6 +127,36 @@ namespace osu.Game.Database
private void onMigration(Migration migration, ulong lastSchemaVersion)
{
+ if (lastSchemaVersion < 7)
+ {
+ convertOnlineIDs();
+ convertOnlineIDs();
+ convertOnlineIDs();
+
+ void convertOnlineIDs() where T : RealmObject
+ {
+ var className = typeof(T).Name.Replace(@"Realm", string.Empty);
+
+ // version was not bumped when the beatmap/ruleset models were added
+ // therefore we must manually check for their presence to avoid throwing on the `DynamicApi` calls.
+ if (!migration.OldRealm.Schema.TryFindObjectSchema(className, out _))
+ return;
+
+ var oldItems = migration.OldRealm.DynamicApi.All(className);
+ var newItems = migration.NewRealm.DynamicApi.All(className);
+
+ int itemCount = newItems.Count();
+
+ for (int i = 0; i < itemCount; i++)
+ {
+ var oldItem = oldItems.ElementAt(i);
+ var newItem = newItems.ElementAt(i);
+
+ long? nullableOnlineID = oldItem?.OnlineID;
+ newItem.OnlineID = (int)(nullableOnlineID ?? -1);
+ }
+ }
+ }
}
///
diff --git a/osu.Game/Models/RealmBeatmap.cs b/osu.Game/Models/RealmBeatmap.cs
index 5049c1384d..9311425cb7 100644
--- a/osu.Game/Models/RealmBeatmap.cs
+++ b/osu.Game/Models/RealmBeatmap.cs
@@ -44,7 +44,8 @@ namespace osu.Game.Models
[MapTo(nameof(Status))]
public int StatusInt { get; set; }
- public int? OnlineID { get; set; }
+ [Indexed]
+ public int OnlineID { get; set; } = -1;
public double Length { get; set; }
diff --git a/osu.Game/Models/RealmBeatmapSet.cs b/osu.Game/Models/RealmBeatmapSet.cs
index 314ca4494b..d6e56fd61c 100644
--- a/osu.Game/Models/RealmBeatmapSet.cs
+++ b/osu.Game/Models/RealmBeatmapSet.cs
@@ -20,7 +20,8 @@ namespace osu.Game.Models
[PrimaryKey]
public Guid ID { get; set; } = Guid.NewGuid();
- public int? OnlineID { get; set; }
+ [Indexed]
+ public int OnlineID { get; set; } = -1;
public DateTimeOffset DateAdded { get; set; }
@@ -62,7 +63,7 @@ namespace osu.Game.Models
if (IsManaged && other.IsManaged)
return ID == other.ID;
- if (OnlineID.HasValue && other.OnlineID.HasValue)
+ if (OnlineID >= 0 && other.OnlineID >= 0)
return OnlineID == other.OnlineID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
diff --git a/osu.Game/Models/RealmRuleset.cs b/osu.Game/Models/RealmRuleset.cs
index 0dcd701ed2..5d70324713 100644
--- a/osu.Game/Models/RealmRuleset.cs
+++ b/osu.Game/Models/RealmRuleset.cs
@@ -18,7 +18,8 @@ namespace osu.Game.Models
[PrimaryKey]
public string ShortName { get; set; } = string.Empty;
- public int? OnlineID { get; set; }
+ [Indexed]
+ public int OnlineID { get; set; } = -1;
public string Name { get; set; } = string.Empty;
@@ -29,7 +30,7 @@ namespace osu.Game.Models
ShortName = shortName;
Name = name;
InstantiationInfo = instantiationInfo;
- OnlineID = onlineID;
+ OnlineID = onlineID ?? -1;
}
[UsedImplicitly]
@@ -39,7 +40,7 @@ namespace osu.Game.Models
public RealmRuleset(int? onlineID, string name, string shortName, bool available)
{
- OnlineID = onlineID;
+ OnlineID = onlineID ?? -1;
Name = name;
ShortName = shortName;
Available = available;
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index ca6a083a58..8cd3fa8c63 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets
#region Implementation of IHasOnlineID
- public int? OnlineID => ID;
+ public int OnlineID => ID ?? -1;
#endregion
}
diff --git a/osu.Game/Screens/Play/Break/BreakInfo.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs
index 6e129b20ea..6349ebd9a7 100644
--- a/osu.Game/Screens/Play/Break/BreakInfo.cs
+++ b/osu.Game/Screens/Play/Break/BreakInfo.cs
@@ -13,7 +13,9 @@ namespace osu.Game.Screens.Play.Break
public class BreakInfo : Container
{
public PercentageBreakInfoLine AccuracyDisplay;
- public BreakInfoLine RankDisplay;
+
+ // Currently unused but may be revisited in a future design update (see https://github.com/ppy/osu/discussions/15185)
+ // public BreakInfoLine RankDisplay;
public BreakInfoLine GradeDisplay;
public BreakInfo()
@@ -41,7 +43,9 @@ namespace osu.Game.Screens.Play.Break
Children = new Drawable[]
{
AccuracyDisplay = new PercentageBreakInfoLine("Accuracy"),
- RankDisplay = new BreakInfoLine("Rank"),
+
+ // See https://github.com/ppy/osu/discussions/15185
+ // RankDisplay = new BreakInfoLine("Rank"),
GradeDisplay = new BreakInfoLine("Grade"),
},
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index c8b946b7f1..1381493fdf 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -218,12 +217,11 @@ namespace osu.Game.Screens.Play
Score = CreateScore(playableBeatmap);
- Debug.Assert(ruleset.RulesetInfo.ID != null);
-
// ensure the score is in a consistent state with the current player.
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
- Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.ID.Value;
+ if (ruleset.RulesetInfo.ID != null)
+ Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.ID.Value;
Score.ScoreInfo.Mods = gameplayMods;
dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score));