Various updates to ruleset and primary key usages to move closer to realm support

This commit is contained in:
Dean Herbert 2021-11-24 12:16:08 +09:00
parent b77bb2f12b
commit b8cd3cdbbc
22 changed files with 64 additions and 73 deletions

View File

@ -505,7 +505,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
using var store = new RealmRulesetStore(realmFactory, storage);
using var store = new RulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);

View File

@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.Centre);
});
AddAssert("song 1 is 5th", () => beatmapSets[4] == first);
AddAssert("song 1 is 5th", () => beatmapSets[4].Equals(first));
AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left));
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps
}
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
public BeatmapMetadata Metadata => BeatmapInfo.Metadata;
public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();

View File

@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps
/// Handles general operations related to global beatmap management.
/// </summary>
[ExcludeFromDynamicCompile]
public class BeatmapManager : IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, BeatmapSetFileInfo>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
public class BeatmapManager : IModelManager<BeatmapSetInfo>, IModelFileManager<BeatmapSetInfo, RealmNamedFileUsage>, IModelImporter<BeatmapSetInfo>, IWorkingBeatmapCache, IDisposable
{
public ITrackStore BeatmapTrackStore { get; }
@ -294,12 +294,12 @@ namespace osu.Game.Beatmaps
#region Implementation of IModelFileManager<in BeatmapSetInfo,in BeatmapSetFileInfo>
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents)
public void ReplaceFile(BeatmapSetInfo model, RealmNamedFileUsage file, Stream contents)
{
beatmapModelManager.ReplaceFile(model, file, contents);
}
public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file)
public void DeleteFile(BeatmapSetInfo model, RealmNamedFileUsage file)
{
beatmapModelManager.DeleteFile(model, file);
}

View File

@ -2,6 +2,7 @@
// 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;
@ -16,6 +17,7 @@ using osu.Framework.Threading;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Stores;
using SharpCompress.Compressors;
using SharpCompress.Compressors.BZip2;
@ -83,15 +85,14 @@ namespace osu.Game.Beatmaps
if (res != null)
{
beatmapInfo.Status = res.Status;
Debug.Assert(beatmapInfo.BeatmapSet != null);
beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None;
beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID;
beatmapInfo.OnlineID = res.OnlineID;
if (beatmapInfo.Metadata != null)
beatmapInfo.Metadata.AuthorID = res.AuthorID;
if (beatmapInfo.BeatmapSet.Metadata != null)
beatmapInfo.BeatmapSet.Metadata.AuthorID = res.AuthorID;
beatmapInfo.Metadata.Author.OnlineID = res.AuthorID;
logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}.");
}
@ -185,15 +186,14 @@ namespace osu.Game.Beatmaps
var status = (BeatmapOnlineStatus)reader.GetByte(2);
beatmapInfo.Status = status;
Debug.Assert(beatmapInfo.BeatmapSet != null);
beatmapInfo.BeatmapSet.Status = status;
beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0);
beatmapInfo.OnlineID = reader.GetInt32(1);
if (beatmapInfo.Metadata != null)
beatmapInfo.Metadata.AuthorID = reader.GetInt32(3);
if (beatmapInfo.BeatmapSet.Metadata != null)
beatmapInfo.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3);
beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3);
logForModel(set, $"Cached local retrieval for {beatmapInfo}.");
return true;
@ -211,7 +211,7 @@ namespace osu.Game.Beatmaps
}
private void logForModel(BeatmapSetInfo set, string message) =>
ArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>.LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}");
RealmArchiveModelImporter<BeatmapSetInfo>.LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}");
public void Dispose()
{

View File

@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps
// if there are no files, presume the full beatmap info has not yet been fetched from the database.
if (beatmapInfo?.BeatmapSet?.Files.Count == 0)
{
int lookupId = beatmapInfo.ID;
var lookupId = beatmapInfo.ID;
beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId);
}
@ -93,7 +93,8 @@ namespace osu.Game.Beatmaps
if (working != null)
return working;
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
// TODO: is this still required..?
//beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));

View File

@ -11,7 +11,7 @@ namespace osu.Game.Database
{
protected readonly Storage Storage;
protected readonly IDatabaseContextFactory ContextFactory;
protected readonly RealmContextFactory ContextFactory;
/// <summary>
/// Refresh an instance potentially from a different thread with a local context-tracked instance.
@ -36,7 +36,7 @@ namespace osu.Game.Database
}
}
protected DatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
protected DatabaseBackedStore(RealmContextFactory contextFactory, Storage storage = null)
{
ContextFactory = contextFactory;
Storage = storage;

View File

@ -14,7 +14,7 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
public abstract class ModelDownloader<TModel, T> : IModelDownloader<T>
where TModel : class, IHasPrimaryKey, ISoftDelete, IEquatable<TModel>, T
where TModel : class, IHasGuidPrimaryKey, ISoftDelete, IEquatable<TModel>, T
where T : class
{
public Action<Notification> PostNotification { protected get; set; }

View File

@ -78,7 +78,7 @@ namespace osu.Game.Input.Bindings
{
var defaults = DefaultKeyBindings.ToList();
if (ruleset != null && !ruleset.ID.HasValue)
if (ruleset != null && !ruleset.IsManaged)
// some tests instantiate a ruleset which is not present in the database.
// in these cases we still want key bindings to work, but matching to database instances would result in none being present,
// so let's populate the defaults directly.

View File

@ -190,9 +190,6 @@ namespace osu.Game
runMigrations();
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
dependencies.CacheAs<IRulesetStore>(RulesetStore);
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client", contextFactory));
new EFToRealmMigrator(contextFactory, realmFactory, LocalConfig).Run();
@ -227,24 +224,12 @@ namespace osu.Game
dependencies.Cache(RulesetStore = new RulesetStore(realmFactory, Storage));
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, contextFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, realmFactory, Scheduler, Host, () => difficultyCache, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realmFactory, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API));
// this should likely be moved to ArchiveModelManager when another case appears where it is necessary
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
// allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete.
List<ScoreInfo> getBeatmapScores(BeatmapSetInfo set)
{
var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList();
return ScoreManager.QueryScores(s => beatmapIds.Contains(s.BeatmapInfo.ID)).ToList();
}
BeatmapManager.ItemRemoved += item => ScoreManager.Delete(getBeatmapScores(item), true);
BeatmapManager.ItemUpdated += item => ScoreManager.Undelete(getBeatmapScores(item), true);
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
AddInternal(difficultyCache);
@ -438,7 +423,9 @@ namespace osu.Game
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
{
if (r.NewValue?.Available != true)
Ruleset instance;
if (r.NewValue?.Available != true || (instance = r.NewValue.CreateInstance()) == null)
{
// reject the change if the ruleset is not available.
Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First();
@ -448,7 +435,9 @@ namespace osu.Game
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
foreach (ModType type in Enum.GetValues(typeof(ModType)))
dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList();
{
dict[type] = instance.GetModsFor(type).ToList();
}
if (!SelectedMods.Disabled)
SelectedMods.Value = Array.Empty<Mod>();
@ -489,7 +478,6 @@ namespace osu.Game
contextFactory?.FlushConnections();
realmRulesetStore?.Dispose();
realmFactory?.Dispose();
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets
throw new RulesetLoadException(@"Instantiation failure");
// overwrite the pre-populated RulesetInfo with a potentially database attached copy.
ruleset.RulesetInfo = this;
// ruleset.RulesetInfo = this;
return ruleset;
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Verify
[BackgroundDependencyLoader]
private void load()
{
InterpretedDifficulty.Default = EditorBeatmap.BeatmapInfo.DifficultyRating;
InterpretedDifficulty.Default = BeatmapDifficultyCache.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating);
InterpretedDifficulty.SetDefault();
IssueList = new IssueList();

View File

@ -226,8 +226,8 @@ namespace osu.Game.Screens.Play
// ensure the score is in a consistent state with the current player.
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
if (ruleset.RulesetInfo.ID != null)
Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.ID.Value;
if (ruleset.RulesetInfo.OnlineID >= 0)
Score.ScoreInfo.RulesetID = ruleset.RulesetInfo.OnlineID;
Score.ScoreInfo.Mods = gameplayMods;
dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score));
@ -488,6 +488,9 @@ namespace osu.Game.Screens.Play
var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
ruleset = rulesetInfo.CreateInstance();
if (ruleset == null)
throw new RulesetLoadException("Instantiation failure");
try
{
playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, gameplayMods);

View File

@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play
protected override APIRequest<APIScoreToken> CreateTokenRequest()
{
int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID ?? -1;
int beatmapId = Beatmap.Value.BeatmapInfo.OnlineID;
int rulesetId = Ruleset.Value.OnlineID;
if (beatmapId <= 0)

View File

@ -197,7 +197,8 @@ namespace osu.Game.Screens.Select
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
{
int? previouslySelectedID = null;
Guid? previouslySelectedID = null;
CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet));
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
@ -626,9 +627,9 @@ namespace osu.Game.Screens.Select
if (beatmapSet.Beatmaps.All(b => b.Hidden))
return null;
// todo: remove the need for this.
foreach (var b in beatmapSet.Beatmaps)
b.Metadata ??= beatmapSet.Metadata;
// todo: probably not required any more.
// foreach (var b in beatmapSet.Beatmaps)
// b.Metadata ??= beatmapSet.Metadata;
var set = new CarouselBeatmapSet(beatmapSet)
{

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel
if (LastSelected == null || LastSelected.Filtered.Value)
{
if (GetRecommendedBeatmap?.Invoke(Children.OfType<CarouselBeatmap>().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended)
return Children.OfType<CarouselBeatmap>().First(b => b.BeatmapInfo == recommended);
return Children.OfType<CarouselBeatmap>().First(b => b.BeatmapInfo.Equals(recommended));
}
return base.GetNextToSelect();
@ -63,16 +63,16 @@ namespace osu.Game.Screens.Select.Carousel
{
default:
case SortMode.Artist:
return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase);
return string.Compare(BeatmapSet.Metadata?.Artist, otherSet.BeatmapSet.Metadata?.Artist, StringComparison.OrdinalIgnoreCase);
case SortMode.Title:
return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase);
return string.Compare(BeatmapSet.Metadata?.Title, otherSet.BeatmapSet.Metadata?.Title, StringComparison.OrdinalIgnoreCase);
case SortMode.Author:
return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase);
return string.Compare(BeatmapSet.Metadata?.Author.Username, otherSet.BeatmapSet.Metadata?.Author.Username, StringComparison.OrdinalIgnoreCase);
case SortMode.Source:
return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase);
return string.Compare(BeatmapSet.Metadata?.Source, otherSet.BeatmapSet.Metadata?.Source, StringComparison.OrdinalIgnoreCase);
case SortMode.DateAdded:
return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);

View File

@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel
{
new OsuSpriteText
{
Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title),
Text = new RomanisableString(beatmapSet.Metadata?.TitleUnicode, beatmapSet.Metadata?.Title),
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
Shadow = true,
},
new OsuSpriteText
{
Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist),
Text = new RomanisableString(beatmapSet.Metadata?.ArtistUnicode, beatmapSet.Metadata?.Artist),
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
Shadow = true,
},

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -37,8 +36,6 @@ namespace osu.Game.Screens.Select
public FilterCriteria CreateCriteria()
{
Debug.Assert(ruleset.Value.ID != null);
string query = searchTextBox.Text;
var criteria = new FilterCriteria
@ -56,7 +53,7 @@ namespace osu.Game.Screens.Select
if (!maximumStars.IsDefault)
criteria.UserStarDifficulty.Max = maximumStars.Value;
criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria();
criteria.RulesetCriteria = ruleset.Value.CreateInstance()?.CreateRulesetFilterCriteria();
FilterQueryParser.ApplyQueries(criteria, query);
return criteria;

View File

@ -33,7 +33,7 @@ namespace osu.Game.Screens.Select.Leaderboards
get => beatmapInfo;
set
{
if (beatmapInfo == value)
if (beatmapInfo.Equals(value))
return;
beatmapInfo = value;
@ -154,7 +154,7 @@ namespace osu.Game.Screens.Select.Leaderboards
return null;
}
if (fetchBeatmapInfo.OnlineID == null || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending)
{
PlaceholderState = PlaceholderState.Unavailable;
return null;

View File

@ -482,7 +482,7 @@ namespace osu.Game.Screens.Select
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
if (beatmap != beatmapInfoPrevious)
if (!beatmap.Equals(beatmapInfoPrevious))
{
if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50)
{

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
@ -78,7 +79,11 @@ namespace osu.Game.Tests.Beatmaps
currentTestBeatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
// populate ruleset for beatmap converters that require it to be present.
currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID);
var ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID);
Debug.Assert(ruleset != null);
currentTestBeatmap.BeatmapInfo.Ruleset = ruleset;
});
});
@ -94,11 +99,7 @@ namespace osu.Game.Tests.Beatmaps
userSkinInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = userFile }, userFile));
beatmapInfo.BeatmapSet.Files.Clear();
beatmapInfo.BeatmapSet.Files.Add(new BeatmapSetFileInfo
{
Filename = beatmapFile,
FileInfo = new IO.FileInfo { Hash = beatmapFile }
});
beatmapInfo.BeatmapSet.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = beatmapFile }, beatmapFile));
// Need to refresh the cached skin source to refresh the skin resource store.
dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, this));

View File

@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual
{
private readonly WorkingBeatmap testBeatmap;
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap)
public TestBeatmapManager(Storage storage, RealmContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host, WorkingBeatmap defaultBeatmap, WorkingBeatmap testBeatmap)
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
{
this.testBeatmap = testBeatmap;