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));