Update many score-related classes to move closer to being able to persist to realm

This commit is contained in:
Dean Herbert 2021-12-06 22:47:00 +09:00
parent e44751c275
commit 2a4bee61dd
14 changed files with 58 additions and 61 deletions

View File

@ -24,7 +24,7 @@ public override void ExportModelTo(ScoreInfo model, Stream outputStream)
if (file == null)
return;
using (var inputStream = UserFileStorage.GetStream(file.FileInfo.GetStoragePath()))
using (var inputStream = UserFileStorage.GetStream(file.File.GetStoragePath()))
inputStream.CopyTo(outputStream);
}
}

View File

@ -105,7 +105,6 @@ public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = nu
OnlineID = OnlineID,
Date = Date,
PP = PP,
RulesetID = RulesetID,
Hash = HasReplay ? "online" : string.Empty, // todo: temporary?
Rank = Rank,
Ruleset = ruleset,

View File

@ -73,9 +73,7 @@ public ScoreInfo CreateScoreInfo(RulesetStore rulesets, PlaylistItem playlistIte
TotalScore = TotalScore,
MaxCombo = MaxCombo,
BeatmapInfo = beatmap,
BeatmapInfoID = playlistItem.BeatmapID,
Ruleset = rulesets.GetRuleset(playlistItem.RulesetID),
RulesetID = playlistItem.RulesetID,
Statistics = Statistics,
User = User,
Accuracy = Accuracy,

View File

@ -10,6 +10,7 @@
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Users;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Online.Solo
@ -48,7 +49,7 @@ public class SubmittableScore
public APIMod[] Mods { get; set; }
[JsonProperty("user")]
public APIUser User { get; set; }
public IUser User { get; set; }
[JsonProperty("statistics")]
public Dictionary<HitResult, int> Statistics { get; set; }

View File

@ -481,7 +481,7 @@ public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate<BeatmapInfo> diffi
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
/// </summary>
public void PresentScore(ScoreInfo score, ScorePresentType presentType = ScorePresentType.Results)
public void PresentScore(IScoreInfo score, ScorePresentType presentType = ScorePresentType.Results)
{
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
// to ensure all the required data for presenting a replay are present.

View File

@ -23,6 +23,8 @@ public class EFScoreInfo : IScoreInfo, IHasFiles<ScoreFileInfo>, IHasPrimaryKey,
{
public int ID { get; set; }
public bool IsManaged => ID > 0;
public ScoreRank Rank { get; set; }
public long TotalScore { get; set; }

View File

@ -17,7 +17,7 @@ public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManag
{
ScoreInfo = score;
string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.GetStoragePath();
string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.File.GetStoragePath();
if (replayFilename == null)
return;

View File

@ -6,6 +6,7 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
@ -15,6 +16,7 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using osu.Game.Utils;
using Realms;
#nullable enable
@ -39,7 +41,18 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD
[Indexed]
public long OnlineID { get; set; } = -1;
public RealmUser User { get; set; } = null!;
[MapTo("User")]
public RealmUser RealmUser { get; set; } = null!;
public IUser User
{
get => RealmUser;
set => RealmUser = new RealmUser
{
OnlineID = value.OnlineID,
Username = value.Username
};
}
public long TotalScore { get; set; }
@ -55,11 +68,20 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD
public RealmBeatmap Beatmap { get; set; } = null!;
public BeatmapInfo BeatmapInfo
{
get => new BeatmapInfo();
// .. todo
set => Beatmap = new RealmBeatmap(new RealmRuleset("osu", "osu!", "wangs", 0), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
}
public RealmRuleset Ruleset { get; set; } = null!;
[Ignored]
public Dictionary<HitResult, int> Statistics
{
// TODO: this is dangerous. a get operation may then modify the dictionary, which would be a fresh copy that is not persisted with the model.
// this is already the case in multiple locations.
get
{
if (string.IsNullOrEmpty(StatisticsJson))
@ -93,6 +115,12 @@ public ScoreRank Rank
private Mod[]? mods;
public int BeatmapInfoID => BeatmapInfo.ID;
public int UserID => RealmUser.OnlineID;
public int RulesetID => Ruleset.OnlineID;
[Ignored]
public List<HitEvent> HitEvents { get; set; } = new List<HitEvent>();
@ -108,12 +136,18 @@ public ScoreInfo DeepClone()
[Ignored]
public bool Passed { get; set; } = true;
[Ignored]
public int Combo { get; set; }
/// <summary>
/// The position of this score, starting at 1.
/// </summary>
[Ignored]
public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone.
[Ignored]
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
/// <summary>
/// Whether this <see cref="EFScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary>

View File

@ -63,7 +63,7 @@ public async Task<ScoreInfo[]> OrderByTotalScoreAsync(ScoreInfo[] scores, Cancel
// Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below.
foreach (var s in scores)
{
await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false);
await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
}
}
@ -125,7 +125,7 @@ public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> call
/// <returns>The total score.</returns>
public async Task<long> GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
{
if (score.BeatmapInfo == null)
if (score.Beatmap == null)
return score.TotalScore;
int beatmapMaxCombo;
@ -146,11 +146,11 @@ public async Task<long> GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMod
// This score is guaranteed to be an osu!stable score.
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
if (score.BeatmapInfo.MaxCombo != null)
beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value;
if (score.Beatmap.MaxCombo != null)
beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
else
{
if (score.BeatmapInfo.ID == 0 || difficulties == null)
if (score.Beatmap.ID == 0 || difficulties == null)
{
// We don't have enough information (max combo) to compute the score, so use the provided score.
return score.TotalScore;

View File

@ -4,10 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
@ -15,10 +13,14 @@
using osu.Game.IO.Archives;
using osu.Game.Rulesets;
using osu.Game.Scoring.Legacy;
using osu.Game.Stores;
using Realms;
#nullable enable
namespace osu.Game.Scoring
{
public class ScoreModelManager : ArchiveModelManager<ScoreInfo, ScoreFileInfo>
public class ScoreModelManager : RealmArchiveModelManager<ScoreInfo>
{
public override IEnumerable<string> HandledExtensions => new[] { ".osr" };
@ -27,18 +29,15 @@ public class ScoreModelManager : ArchiveModelManager<ScoreInfo, ScoreFileInfo>
private readonly RulesetStore rulesets;
private readonly Func<BeatmapManager> beatmaps;
public ScoreModelManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
: base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost)
public ScoreModelManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmContextFactory contextFactory)
: base(storage, contextFactory)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
}
protected override ScoreInfo CreateModel(ArchiveReader archive)
protected override ScoreInfo? CreateModel(ArchiveReader archive)
{
if (archive == null)
return null;
using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr", StringComparison.OrdinalIgnoreCase))))
{
try
@ -55,17 +54,7 @@ protected override ScoreInfo CreateModel(ArchiveReader archive)
public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store);
public List<ScoreInfo> GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
public IEnumerable<ScoreInfo> QueryScores(Expression<Func<ScoreInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query);
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
protected override Task Populate(ScoreInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
protected override Task Populate(ScoreInfo model, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
=> base.CheckLocalAvailability(model, items)
|| (model.OnlineID > 0 && items.Any(i => i.OnlineID == model.OnlineID));
}
}

View File

@ -1,25 +0,0 @@
// 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.
using System.Linq;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Platform;
using osu.Game.Database;
namespace osu.Game.Scoring
{
public class ScoreStore : MutableDatabaseBackedStoreWithFileIncludes<ScoreInfo, ScoreFileInfo>
{
public ScoreStore(IDatabaseContextFactory factory, Storage storage)
: base(factory, storage)
{
}
protected override IQueryable<ScoreInfo> AddIncludesForConsumption(IQueryable<ScoreInfo> query)
=> base.AddIncludesForConsumption(query)
.Include(s => s.BeatmapInfo)
.Include(s => s.BeatmapInfo).ThenInclude(b => b.Metadata)
.Include(s => s.BeatmapInfo).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
.Include(s => s.Ruleset);
}
}

View File

@ -196,7 +196,7 @@ private void selectedScoreChanged(ValueChangedEvent<ScoreInfo> score)
}
// Find the panel corresponding to the new score.
var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue);
var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score.Equals(score.NewValue));
expandedPanel = expandedTrackingComponent?.Panel;
if (expandedPanel == null)

View File

@ -127,7 +127,7 @@ protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresC
if (Scope == BeatmapLeaderboardScope.Local)
{
var scores = scoreManager
.QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.ID == ruleset.Value.ID);
.QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == fetchBeatmapInfo.ID && s.Ruleset.OnlineID == ruleset.Value.ID);
if (filterMods && !mods.Value.Any())
{

View File

@ -12,7 +12,6 @@
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using Realms;