1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

Merge pull request #19907 from peppy/ruleset-api-versioning

Add ruleset API versioning
This commit is contained in:
Dan Balasescu 2022-08-29 18:50:01 +09:00 committed by GitHub
commit e3e204001e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 247 additions and 77 deletions

View File

@ -77,5 +77,8 @@ namespace osu.Game.Rulesets.EmptyFreeform
};
}
}
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
}
}

View File

@ -49,5 +49,8 @@ namespace osu.Game.Rulesets.Pippidon
};
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
}
}

View File

@ -54,5 +54,8 @@ namespace osu.Game.Rulesets.EmptyScrolling
Text = ShortName[0].ToString(),
Font = OsuFont.Default.With(size: 18),
};
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
}
}

View File

@ -46,5 +46,8 @@ namespace osu.Game.Rulesets.Pippidon
};
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
}
}

View File

@ -3,30 +3,30 @@
#nullable disable
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Catch.Edit;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Catch
public const string SHORT_NAME = "fruits";
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),

View File

@ -4,11 +4,6 @@
#nullable disable
using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
@ -16,11 +11,10 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
@ -31,13 +25,19 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Edit.Setup;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania
{
@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Mania
public const string SHORT_NAME = "mania";
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap);

View File

@ -3,42 +3,42 @@
#nullable disable
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using System;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Setup;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu
{
@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Osu
public const string SHORT_NAME = "osu";
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),

View File

@ -3,33 +3,33 @@
#nullable disable
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
using System;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Taiko
public const string SHORT_NAME = "taiko";
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),

View File

@ -1,11 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Tests.Database
{
@ -51,5 +59,105 @@ namespace osu.Game.Tests.Database
Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged);
});
}
[Test]
public void TestRulesetThrowingOnMethods()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
LoadTestRuleset.HasImplementations = false;
var ruleset = new LoadTestRuleset();
string rulesetShortName = ruleset.RulesetInfo.ShortName;
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, ruleset.RulesetInfo.OnlineID)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName).Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName).Available), Is.False);
});
}
[Test]
public void TestOutdatedRulesetNotAvailable()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.Version = "2021.101.0";
LoadTestRuleset.HasImplementations = true;
var ruleset = new LoadTestRuleset();
string rulesetShortName = ruleset.RulesetInfo.ShortName;
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, ruleset.RulesetInfo.OnlineID)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName).Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName).Available), Is.False);
// Simulate the ruleset getting updated
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
var __ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName).Available), Is.True);
});
}
private class LoadTestRuleset : Ruleset
{
public override string RulesetAPIVersionSupported => Version;
public static bool HasImplementations = true;
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
public override IEnumerable<Mod> GetModsFor(ModType type)
{
if (!HasImplementations)
throw new NotImplementedException();
return Array.Empty<Mod>();
}
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null)
{
if (!HasImplementations)
throw new NotImplementedException();
return new DrawableOsuRuleset(new OsuRuleset(), beatmap, mods);
}
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap)
{
if (!HasImplementations)
throw new NotImplementedException();
return new OsuBeatmapConverter(beatmap, new OsuRuleset());
}
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap)
{
if (!HasImplementations)
throw new NotImplementedException();
return new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap);
}
public override string Description => "outdated ruleset";
public override string ShortName => "ruleset-outdated";
}
}
}

View File

@ -77,9 +77,16 @@ namespace osu.Game.Rulesets
continue;
}
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
var instance = (Activator.CreateInstance(resolvedType) as Ruleset);
var instanceInfo = instance?.RulesetInfo
?? throw new RulesetLoadException(@"Instantiation failure");
if (!checkRulesetUpToDate(instance))
{
throw new ArgumentOutOfRangeException(nameof(instance.RulesetAPIVersionSupported),
$"Ruleset API version is too old (was {instance.RulesetAPIVersionSupported}, expected {Ruleset.CURRENT_RULESET_API_VERSION})");
}
// If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
// To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
resolvedType.Assembly.GetTypes();
@ -104,6 +111,23 @@ namespace osu.Game.Rulesets
});
}
private bool checkRulesetUpToDate(Ruleset instance)
{
switch (instance.RulesetAPIVersionSupported)
{
// The default `virtual` implementation leaves the version string empty.
// Consider rulesets which haven't override the version as up-to-date for now.
// At some point (once ruleset devs add versioning), we'll probably want to disallow this for deployed builds.
case @"":
// Ruleset is up-to-date, all good.
case Ruleset.CURRENT_RULESET_API_VERSION:
return true;
default:
return false;
}
}
private void testRulesetCompatibility(RulesetInfo rulesetInfo)
{
// do various operations to ensure that we are in a good state.

View File

@ -7,33 +7,33 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Users;
using JetBrains.Annotations;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
using osu.Game.Users;
namespace osu.Game.Rulesets
{
@ -44,6 +44,24 @@ namespace osu.Game.Rulesets
private static readonly ConcurrentDictionary<string, IMod[]> mod_reference_cache = new ConcurrentDictionary<string, IMod[]>();
/// <summary>
/// Version history:
/// 2022.205.0 FramedReplayInputHandler.CollectPendingInputs renamed to FramedReplayHandler.CollectReplayInputs.
/// 2022.822.0 All strings return values have been converted to LocalisableString to allow for localisation support.
/// </summary>
public const string CURRENT_RULESET_API_VERSION = "2022.822.0";
/// <summary>
/// Define the ruleset API version supported by this ruleset.
/// Ruleset implementations should be updated to support the latest version to ensure they can still be loaded.
/// </summary>
/// <remarks>
/// Generally, all ruleset implementations should point this directly to <see cref="CURRENT_RULESET_API_VERSION"/>.
/// This will ensure that each time you compile a new release, it will pull in the most recent version.
/// See https://github.com/ppy/osu/wiki/Breaking-Changes for full details on required ongoing changes.
/// </remarks>
public virtual string RulesetAPIVersionSupported => string.Empty;
/// <summary>
/// A queryable source containing all available mods.
/// Call <see cref="IMod.CreateInstance"/> for consumption purposes.