1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-14 02:35:34 +08:00

Merge branch 'master' into beatmap-card/download-button

This commit is contained in:
Dean Herbert 2021-11-21 20:04:26 +09:00 committed by GitHub
commit c0962b1c4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 211 additions and 150 deletions

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.EmptyFreeform
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
return new DifficultyAttributes(mods, skills, 0); return new DifficultyAttributes(mods, 0);
} }
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>(); protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Pippidon
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
return new DifficultyAttributes(mods, skills, 0); return new DifficultyAttributes(mods, 0);
} }
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>(); protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.EmptyScrolling
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
return new DifficultyAttributes(mods, skills, 0); return new DifficultyAttributes(mods, 0);
} }
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>(); protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Pippidon
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
return new DifficultyAttributes(mods, skills, 0); return new DifficultyAttributes(mods, 0);
} }
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>(); protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)
return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; return new CatchDifficultyAttributes { Mods = mods };
// this is the same as osu!, so there's potential to share the implementation... maybe // this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
Mods = mods, Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)), MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
Skills = skills
}; };
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills }; return new ManiaDifficultyAttributes { Mods = mods };
HitWindows hitWindows = new ManiaHitWindows(); HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate), GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate),
ScoreMultiplier = getScoreMultiplier(mods), ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
Skills = skills
}; };
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)
return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; return new OsuDifficultyAttributes { Mods = mods };
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
@ -85,7 +85,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
HitCircleCount = hitCirclesCount, HitCircleCount = hitCirclesCount,
SliderCount = sliderCount, SliderCount = sliderCount,
SpinnerCount = spinnerCount, SpinnerCount = spinnerCount,
Skills = skills
}; };
} }

View File

@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{ {
if (beatmap.HitObjects.Count == 0) if (beatmap.HitObjects.Count == 0)
return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; return new TaikoDifficultyAttributes { Mods = mods };
var colour = (Colour)skills[0]; var colour = (Colour)skills[0];
var rhythm = (Rhythm)skills[1]; var rhythm = (Rhythm)skills[1];
@ -96,7 +96,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
ColourStrain = colourRating, ColourStrain = colourRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit), MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
Skills = skills
}; };
} }

View File

@ -16,6 +16,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -363,15 +364,15 @@ namespace osu.Game.Tests.Beatmaps.IO
var files = osu.Dependencies.Get<FileStore>(); var files = osu.Dependencies.Get<FileStore>();
long originalLength; long originalLength;
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath)) using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
originalLength = stream.Length; originalLength = stream.Length;
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath, FileAccess.Write, FileMode.Create)) using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath(), FileAccess.Write, FileMode.Create))
stream.WriteByte(0); stream.WriteByte(0);
var importedSecondTime = await LoadOszIntoOsu(osu); var importedSecondTime = await LoadOszIntoOsu(osu);
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath)) using (var stream = files.Storage.GetStream(firstFile.FileInfo.GetStoragePath()))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.

View File

@ -164,9 +164,9 @@ namespace osu.Game.Tests.Beatmaps
{ {
public Func<DifficultyCacheLookup, StarDifficulty> ComputeDifficulty { get; set; } public Func<DifficultyCacheLookup, StarDifficulty> ComputeDifficulty { get; set; }
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) protected override Task<StarDifficulty?> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
{ {
return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); return Task.FromResult<StarDifficulty?>(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0));
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Moq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
@ -40,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
loadStarted.Set(); loadStarted.Set();
Assert.Throws<OperationCanceledException>(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, cancellationToken: cts.Token)); Assert.Throws<OperationCanceledException>(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>(), cts.Token));
loadCompleted.Set(); loadCompleted.Set();
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
@ -58,7 +59,7 @@ namespace osu.Game.Tests.Beatmaps
{ {
var working = new TestNeverLoadsWorkingBeatmap(); var working = new TestNeverLoadsWorkingBeatmap();
Assert.Throws<OperationCanceledException>(() => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); Assert.Throws(Is.InstanceOf<TimeoutException>(), () => working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
working.ResetEvent.Set(); working.ResetEvent.Set();
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -348,15 +349,15 @@ namespace osu.Game.Tests.Database
var firstFile = imported.Files.First(); var firstFile = imported.Files.First();
long originalLength; long originalLength;
using (var stream = storage.GetStream(firstFile.File.StoragePath)) using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
originalLength = stream.Length; originalLength = stream.Length;
using (var stream = storage.GetStream(firstFile.File.StoragePath, FileAccess.Write, FileMode.Create)) using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create))
stream.WriteByte(0); stream.WriteByte(0);
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context); var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
using (var stream = storage.GetStream(firstFile.File.StoragePath)) using (var stream = storage.GetStream(firstFile.File.GetStoragePath()))
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import"); Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Extensions;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Stores; using osu.Game.Stores;
@ -28,7 +29,7 @@ namespace osu.Game.Tests.Database
realm.Write(() => files.Add(testData, realm)); realm.Write(() => files.Add(testData, realm));
Assert.True(files.Storage.Exists("0/05/054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8")); Assert.True(files.Storage.Exists("0/05/054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8"));
Assert.True(files.Storage.Exists(realm.All<RealmFile>().First().StoragePath)); Assert.True(files.Storage.Exists(realm.All<RealmFile>().First().GetStoragePath()));
}); });
} }
@ -74,7 +75,7 @@ namespace osu.Game.Tests.Database
Logger.Log($"Import complete at {timer.ElapsedMilliseconds}"); Logger.Log($"Import complete at {timer.ElapsedMilliseconds}");
string path = file.StoragePath; string path = file.GetStoragePath();
Assert.True(realm.All<RealmFile>().Any()); Assert.True(realm.All<RealmFile>().Any());
Assert.True(files.Storage.Exists(path)); Assert.True(files.Storage.Exists(path));
@ -98,7 +99,7 @@ namespace osu.Game.Tests.Database
var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm)); var file = realm.Write(() => files.Add(new MemoryStream(new byte[] { 0, 1, 2, 3 }), realm));
string path = file.StoragePath; string path = file.GetStoragePath();
Assert.True(realm.All<RealmFile>().Any()); Assert.True(realm.All<RealmFile>().Any());
Assert.True(files.Storage.Exists(path)); Assert.True(files.Storage.Exists(path));

View File

@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect
} }
} }
public override async Task<StarDifficulty> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default) public override async Task<StarDifficulty?> GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
{ {
if (blockCalculation) if (blockCalculation)
await calculationBlocker.Task.ConfigureAwait(false); await calculationBlocker.Task.ConfigureAwait(false);

View File

@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps
/// A component which performs and acts as a central cache for difficulty calculations of beatmap/ruleset/mod combinations. /// A component which performs and acts as a central cache for difficulty calculations of beatmap/ruleset/mod combinations.
/// Currently not persisted between game sessions. /// Currently not persisted between game sessions.
/// </summary> /// </summary>
public class BeatmapDifficultyCache : MemoryCachingComponent<BeatmapDifficultyCache.DifficultyCacheLookup, StarDifficulty> public class BeatmapDifficultyCache : MemoryCachingComponent<BeatmapDifficultyCache.DifficultyCacheLookup, StarDifficulty?>
{ {
// Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes.
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache)); private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache));
@ -120,9 +120,13 @@ namespace osu.Game.Beatmaps
/// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to get the difficulty with.</param> /// <param name="rulesetInfo">The <see cref="IRulesetInfo"/> to get the difficulty with.</param>
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param> /// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param>
/// <returns>The <see cref="StarDifficulty"/>.</returns> /// <returns>
public virtual Task<StarDifficulty> GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null, /// The requested <see cref="StarDifficulty"/>, if non-<see langword="null"/>.
[CanBeNull] IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default) /// A <see langword="null"/> return value indicates that the difficulty process failed or was interrupted early,
/// and as such there is no usable star difficulty value to be returned.
/// </returns>
public virtual Task<StarDifficulty?> GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null,
[CanBeNull] IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
{ {
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
rulesetInfo ??= beatmapInfo.Ruleset; rulesetInfo ??= beatmapInfo.Ruleset;
@ -134,13 +138,13 @@ namespace osu.Game.Beatmaps
if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null) if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null)
{ {
// If not, fall back to the existing star difficulty (e.g. from an online source). // If not, fall back to the existing star difficulty (e.g. from an online source).
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); return Task.FromResult<StarDifficulty?>(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
} }
return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken); return GetAsync(new DifficultyCacheLookup(localBeatmapInfo, localRulesetInfo, mods), cancellationToken);
} }
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default) protected override Task<StarDifficulty?> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken cancellationToken = default)
{ {
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() =>
{ {
@ -151,6 +155,8 @@ namespace osu.Game.Beatmaps
}, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
} }
protected override bool CacheNullValues => false;
public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default) public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default)
{ {
return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, cancellationToken), return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods, cancellationToken),
@ -260,7 +266,7 @@ namespace osu.Game.Beatmaps
// We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events.
Schedule(() => Schedule(() =>
{ {
if (!cancellationToken.IsCancellationRequested) if (!cancellationToken.IsCancellationRequested && t.Result != null)
bindable.Value = t.Result; bindable.Value = t.Result;
}); });
}, cancellationToken); }, cancellationToken);
@ -272,7 +278,7 @@ namespace osu.Game.Beatmaps
/// <param name="key">The <see cref="DifficultyCacheLookup"/> that defines the computation parameters.</param> /// <param name="key">The <see cref="DifficultyCacheLookup"/> that defines the computation parameters.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The <see cref="StarDifficulty"/>.</returns> /// <returns>The <see cref="StarDifficulty"/>.</returns>
private StarDifficulty computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default) private StarDifficulty? computeDifficulty(in DifficultyCacheLookup key, CancellationToken cancellationToken = default)
{ {
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
var beatmapInfo = key.BeatmapInfo; var beatmapInfo = key.BeatmapInfo;
@ -288,16 +294,23 @@ namespace osu.Game.Beatmaps
return new StarDifficulty(attributes); return new StarDifficulty(attributes);
} }
catch (BeatmapInvalidForRulesetException e) catch (OperationCanceledException)
{
// no need to log, cancellations are expected as part of normal operation.
return null;
}
catch (BeatmapInvalidForRulesetException invalidForRuleset)
{ {
if (rulesetInfo.Equals(beatmapInfo.Ruleset)) if (rulesetInfo.Equals(beatmapInfo.Ruleset))
Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); Logger.Error(invalidForRuleset, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset}).");
return new StarDifficulty(); return null;
} }
catch catch (Exception unknownException)
{ {
return new StarDifficulty(); Logger.Error(unknownException, "Failed to calculate beatmap difficulty");
return null;
} }
} }

View File

@ -18,6 +18,7 @@ using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -384,7 +385,7 @@ namespace osu.Game.Beatmaps
foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)))
{ {
using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath)) using (var raw = Files.Store.GetStream(file.FileInfo.GetStoragePath()))
using (var ms = new MemoryStream()) // we need a memory stream so we can seek using (var ms = new MemoryStream()) // we need a memory stream so we can seek
using (var sr = new LineBufferedReader(ms)) using (var sr = new LineBufferedReader(ms))
{ {

View File

@ -8,6 +8,7 @@ using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps
/// The path returned is relative to the user file storage. /// The path returned is relative to the user file storage.
/// </summary> /// </summary>
/// <param name="filename">The name of the file to get the storage path of.</param> /// <param name="filename">The name of the file to get the storage path of.</param>
public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public string GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath();
public override string ToString() => Metadata?.ToString() ?? base.ToString(); public override string ToString() => Metadata?.ToString() ?? base.ToString();

View File

@ -90,12 +90,31 @@ namespace osu.Game.Beatmaps
/// have been applied, and <see cref="HitObject"/>s have been fully constructed. /// have been applied, and <see cref="HitObject"/>s have been fully constructed.
/// </para> /// </para>
/// </summary> /// </summary>
/// <remarks>
/// By default, the beatmap load process will be interrupted after 10 seconds.
/// For finer-grained control over the load process, use the
/// <see cref="GetPlayableBeatmap(osu.Game.Rulesets.IRulesetInfo,System.Collections.Generic.IReadOnlyList{osu.Game.Rulesets.Mods.Mod},System.Threading.CancellationToken)"/>
/// overload instead.
/// </remarks>
/// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param> /// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param>
/// <param name="mods">The <see cref="Mod"/>s to apply to the <see cref="IBeatmap"/>.</param> /// <param name="mods">The <see cref="Mod"/>s to apply to the <see cref="IBeatmap"/>.</param>
/// <param name="cancellationToken">Cancellation token that cancels the beatmap loading process. If not provided, a default timeout of 10,000ms will be applied to the load process.</param>
/// <returns>The converted <see cref="IBeatmap"/>.</returns> /// <returns>The converted <see cref="IBeatmap"/>.</returns>
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception> /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null, CancellationToken? cancellationToken = null); IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null);
/// <summary>
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
/// <para>
/// The returned <see cref="IBeatmap"/> is in a playable state - all <see cref="HitObject"/> and <see cref="BeatmapDifficulty"/> <see cref="Mod"/>s
/// have been applied, and <see cref="HitObject"/>s have been fully constructed.
/// </para>
/// </summary>
/// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param>
/// <param name="mods">The <see cref="Mod"/>s to apply to the <see cref="IBeatmap"/>.</param>
/// <param name="cancellationToken">Cancellation token that cancels the beatmap loading process.</param>
/// <returns>The converted <see cref="IBeatmap"/>.</returns>
/// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Load a new audio track instance for this beatmap. This should be called once before accessing <see cref="Track"/>. /// Load a new audio track instance for this beatmap. This should be called once before accessing <see cref="Track"/>.

View File

@ -79,14 +79,24 @@ namespace osu.Game.Beatmaps
/// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns> /// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns>
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
public virtual IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null, CancellationToken? cancellationToken = null) public IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null)
{ {
var token = cancellationToken ?? try
// don't apply the default timeout when debugger is attached (may be breakpointing / debugging). {
(Debugger.IsAttached ? new CancellationToken() : new CancellationTokenSource(10000).Token); using (var cancellationTokenSource = new CancellationTokenSource(10_000))
{
mods ??= Array.Empty<Mod>(); // don't apply the default timeout when debugger is attached (may be breakpointing / debugging).
return GetPlayableBeatmap(ruleset, mods ?? Array.Empty<Mod>(), Debugger.IsAttached ? new CancellationToken() : cancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
throw new BeatmapLoadTimeoutException(BeatmapInfo);
}
}
public virtual IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, [NotNull] IReadOnlyList<Mod> mods, CancellationToken token)
{
var rulesetInstance = ruleset.CreateInstance(); var rulesetInstance = ruleset.CreateInstance();
if (rulesetInstance == null) if (rulesetInstance == null)
@ -101,9 +111,7 @@ namespace osu.Game.Beatmaps
// Apply conversion mods // Apply conversion mods
foreach (var mod in mods.OfType<IApplicableToBeatmapConverter>()) foreach (var mod in mods.OfType<IApplicableToBeatmapConverter>())
{ {
if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToBeatmapConverter(converter); mod.ApplyToBeatmapConverter(converter);
} }
@ -113,9 +121,7 @@ namespace osu.Game.Beatmaps
// Apply conversion mods to the result // Apply conversion mods to the result
foreach (var mod in mods.OfType<IApplicableAfterBeatmapConversion>()) foreach (var mod in mods.OfType<IApplicableAfterBeatmapConversion>())
{ {
if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToBeatmap(converted); mod.ApplyToBeatmap(converted);
} }
@ -124,9 +130,7 @@ namespace osu.Game.Beatmaps
{ {
foreach (var mod in mods.OfType<IApplicableToDifficulty>()) foreach (var mod in mods.OfType<IApplicableToDifficulty>())
{ {
if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToDifficulty(converted.Difficulty); mod.ApplyToDifficulty(converted.Difficulty);
} }
} }
@ -139,28 +143,17 @@ namespace osu.Game.Beatmaps
processor?.PreProcess(); processor?.PreProcess();
// Compute default values for hitobjects, including creating nested hitobjects in-case they're needed // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
try foreach (var obj in converted.HitObjects)
{ {
foreach (var obj in converted.HitObjects) token.ThrowIfCancellationRequested();
{ obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token);
if (token.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, token);
}
}
catch (OperationCanceledException)
{
throw new BeatmapLoadTimeoutException(BeatmapInfo);
} }
foreach (var mod in mods.OfType<IApplicableToHitObject>()) foreach (var mod in mods.OfType<IApplicableToHitObject>())
{ {
foreach (var obj in converted.HitObjects) foreach (var obj in converted.HitObjects)
{ {
if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
throw new BeatmapLoadTimeoutException(BeatmapInfo);
mod.ApplyToHitObject(obj); mod.ApplyToHitObject(obj);
} }
} }

View File

@ -325,7 +325,7 @@ namespace osu.Game.Database
foreach (TFileModel file in hashableFiles) foreach (TFileModel file in hashableFiles)
{ {
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) using (Stream s = Files.Store.GetStream(file.FileInfo.GetStoragePath()))
s.CopyTo(hashable); s.CopyTo(hashable);
} }
@ -480,7 +480,7 @@ namespace osu.Game.Database
using (var archive = ZipArchive.Create()) using (var archive = ZipArchive.Create())
{ {
foreach (var file in model.Files) foreach (var file in model.Files)
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.GetStoragePath()));
archive.SaveTo(outputStream); archive.SaveTo(outputStream);
} }

View File

@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.IO;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -14,6 +16,13 @@ namespace osu.Game.Extensions
{ {
public static class ModelExtensions public static class ModelExtensions
{ {
/// <summary>
/// Get the relative path in osu! storage for this file.
/// </summary>
/// <param name="fileInfo">The file info.</param>
/// <returns>A relative file path.</returns>
public static string GetStoragePath(this IFileInfo fileInfo) => Path.Combine(fileInfo.Hash.Remove(1), fileInfo.Hash.Remove(2), fileInfo.Hash);
/// <summary> /// <summary>
/// Returns a user-facing string representing the <paramref name="model"/>. /// Returns a user-facing string representing the <paramref name="model"/>.
/// </summary> /// </summary>

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.IO;
using osu.Game.Database; using osu.Game.Database;
namespace osu.Game.IO namespace osu.Game.IO
@ -12,8 +11,6 @@ namespace osu.Game.IO
public string Hash { get; set; } public string Hash { get; set; }
public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash);
public int ReferenceCount { get; set; } public int ReferenceCount { get; set; }
} }
} }

View File

@ -12,6 +12,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
namespace osu.Game.IO namespace osu.Game.IO
{ {
@ -47,7 +48,7 @@ namespace osu.Game.IO
var info = existing ?? new FileInfo { Hash = hash }; var info = existing ?? new FileInfo { Hash = hash };
string path = info.StoragePath; string path = info.GetStoragePath();
// we may be re-adding a file to fix missing store entries. // we may be re-adding a file to fix missing store entries.
bool requiresCopy = !Storage.Exists(path); bool requiresCopy = !Storage.Exists(path);
@ -120,7 +121,7 @@ namespace osu.Game.IO
{ {
try try
{ {
Storage.Delete(f.StoragePath); Storage.Delete(f.GetStoragePath());
context.FileInfo.Remove(f); context.FileInfo.Remove(f);
} }
catch (Exception e) catch (Exception e)

View File

@ -52,7 +52,7 @@ namespace osu.Game.Models
/// The path returned is relative to the user file storage. /// The path returned is relative to the user file storage.
/// </summary> /// </summary>
/// <param name="filename">The name of the file to get the storage path of.</param> /// <param name="filename">The name of the file to get the storage path of.</param>
public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.StoragePath; public string? GetPathForFile(string filename) => Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
public bool Equals(RealmBeatmapSet? other) public bool Equals(RealmBeatmapSet? other)
{ {

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.IO;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.IO; using osu.Game.IO;
using Realms; using Realms;
@ -16,7 +15,5 @@ namespace osu.Game.Models
{ {
[PrimaryKey] [PrimaryKey]
public string Hash { get; set; } = string.Empty; public string Hash { get; set; } = string.Empty;
public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash);
} }
} }

View File

@ -17,8 +17,9 @@ namespace osu.Game.Online
public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter public class SignalRDerivedTypeWorkaroundJsonConverter : JsonConverter
{ {
public override bool CanConvert(Type objectType) => public override bool CanConvert(Type objectType) =>
SignalRUnionWorkaroundResolver.BASE_TYPES.Contains(objectType) || SignalRWorkaroundTypes.BASE_TYPE_MAPPING.Any(t =>
SignalRUnionWorkaroundResolver.DERIVED_TYPES.Contains(objectType); objectType == t.baseType ||
objectType == t.derivedType);
public override object? ReadJson(JsonReader reader, Type objectType, object? o, JsonSerializer jsonSerializer) public override object? ReadJson(JsonReader reader, Type objectType, object? o, JsonSerializer jsonSerializer)
{ {
@ -29,7 +30,7 @@ namespace osu.Game.Online
string type = (string)obj[@"$dtype"]!; string type = (string)obj[@"$dtype"]!;
var resolvedType = SignalRUnionWorkaroundResolver.DERIVED_TYPES.Single(t => t.Name == type); var resolvedType = SignalRWorkaroundTypes.BASE_TYPE_MAPPING.Select(t => t.derivedType).Single(t => t.Name == type);
object? instance = Activator.CreateInstance(resolvedType); object? instance = Activator.CreateInstance(resolvedType);

View File

@ -3,11 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using MessagePack; using MessagePack;
using MessagePack.Formatters; using MessagePack.Formatters;
using MessagePack.Resolvers; using MessagePack.Resolvers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online namespace osu.Game.Online
{ {
@ -20,34 +19,22 @@ namespace osu.Game.Online
public static readonly MessagePackSerializerOptions OPTIONS = public static readonly MessagePackSerializerOptions OPTIONS =
MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver()); MessagePackSerializerOptions.Standard.WithResolver(new SignalRUnionWorkaroundResolver());
public static readonly IReadOnlyList<Type> BASE_TYPES = new[] private static readonly IReadOnlyDictionary<Type, IMessagePackFormatter> formatter_map = createFormatterMap();
{
typeof(MatchServerEvent),
typeof(MatchUserRequest),
typeof(MatchRoomState),
typeof(MatchUserState),
};
public static readonly IReadOnlyList<Type> DERIVED_TYPES = new[] private static IReadOnlyDictionary<Type, IMessagePackFormatter> createFormatterMap()
{ {
typeof(ChangeTeamRequest), IEnumerable<(Type derivedType, Type baseType)> baseMap = SignalRWorkaroundTypes.BASE_TYPE_MAPPING;
typeof(TeamVersusRoomState),
typeof(TeamVersusUserState),
};
private static readonly IReadOnlyDictionary<Type, IMessagePackFormatter> formatter_map = new Dictionary<Type, IMessagePackFormatter> // This should not be required. The fallback should work. But something is weird with the way caching is done.
{
{ typeof(TeamVersusUserState), new TypeRedirectingFormatter<TeamVersusUserState, MatchUserState>() },
{ typeof(TeamVersusRoomState), new TypeRedirectingFormatter<TeamVersusRoomState, MatchRoomState>() },
{ typeof(ChangeTeamRequest), new TypeRedirectingFormatter<ChangeTeamRequest, MatchUserRequest>() },
// These should not be required. The fallback should work. But something is weird with the way caching is done.
// For future adventurers, I would not advise looking into this further. It's likely not worth the effort. // For future adventurers, I would not advise looking into this further. It's likely not worth the effort.
{ typeof(MatchUserState), new TypeRedirectingFormatter<MatchUserState, MatchUserState>() }, baseMap = baseMap.Concat(baseMap.Select(t => (t.baseType, t.baseType)));
{ typeof(MatchRoomState), new TypeRedirectingFormatter<MatchRoomState, MatchRoomState>() },
{ typeof(MatchUserRequest), new TypeRedirectingFormatter<MatchUserRequest, MatchUserRequest>() }, return new Dictionary<Type, IMessagePackFormatter>(baseMap.Select(t =>
{ typeof(MatchServerEvent), new TypeRedirectingFormatter<MatchServerEvent, MatchServerEvent>() }, {
}; var formatter = (IMessagePackFormatter)Activator.CreateInstance(typeof(TypeRedirectingFormatter<,>).MakeGenericType(t.derivedType, t.baseType));
return new KeyValuePair<Type, IMessagePackFormatter>(t.derivedType, formatter);
}));
}
public IMessagePackFormatter<T> GetFormatter<T>() public IMessagePackFormatter<T> GetFormatter<T>()
{ {

View File

@ -0,0 +1,25 @@
// 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;
using System.Collections.Generic;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
namespace osu.Game.Online
{
/// <summary>
/// A static class providing the list of types requiring workarounds for serialisation in SignalR.
/// </summary>
/// <seealso cref="SignalRUnionWorkaroundResolver"/>
/// <seealso cref="SignalRDerivedTypeWorkaroundJsonConverter"/>
internal static class SignalRWorkaroundTypes
{
internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[]
{
(typeof(ChangeTeamRequest), typeof(MatchUserRequest)),
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
(typeof(TeamVersusUserState), typeof(MatchUserState)),
};
}
}

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty namespace osu.Game.Rulesets.Difficulty
@ -31,11 +30,6 @@ namespace osu.Game.Rulesets.Difficulty
/// </summary> /// </summary>
public Mod[] Mods { get; set; } public Mod[] Mods { get; set; }
/// <summary>
/// The skills resulting from the difficulty calculation.
/// </summary>
public Skill[] Skills { get; set; }
/// <summary> /// <summary>
/// The combined star rating of all skill. /// The combined star rating of all skill.
/// </summary> /// </summary>
@ -59,12 +53,10 @@ namespace osu.Game.Rulesets.Difficulty
/// Creates new <see cref="DifficultyAttributes"/>. /// Creates new <see cref="DifficultyAttributes"/>.
/// </summary> /// </summary>
/// <param name="mods">The mods which were applied to the beatmap.</param> /// <param name="mods">The mods which were applied to the beatmap.</param>
/// <param name="skills">The skills resulting from the difficulty calculation.</param>
/// <param name="starRating">The combined star rating of all skills.</param> /// <param name="starRating">The combined star rating of all skills.</param>
public DifficultyAttributes(Mod[] mods, Skill[] skills, double starRating) public DifficultyAttributes(Mod[] mods, double starRating)
{ {
Mods = mods; Mods = mods;
Skills = skills;
StarRating = starRating; StarRating = starRating;
} }

View File

@ -11,6 +11,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// <summary> /// <summary>
/// A bare minimal abstract skill for fully custom skill implementations. /// A bare minimal abstract skill for fully custom skill implementations.
/// </summary> /// </summary>
/// <remarks>
/// This class should be considered a "processing" class and not persisted, as it keeps references to
/// gameplay objects after processing is run (see <see cref="Previous"/>).
/// </remarks>
public abstract class Skill public abstract class Skill
{ {
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using ManagedBass; using ManagedBass;
using osu.Framework.Audio.Callbacks; using osu.Framework.Audio.Callbacks;
using osu.Game.Extensions;
using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks namespace osu.Game.Rulesets.Edit.Checks
@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Edit.Checks
foreach (var file in beatmapSet.Files) foreach (var file in beatmapSet.Files)
{ {
using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath)) using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.GetStoragePath()))
{ {
if (data == null) if (data == null)
continue; continue;

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using osu.Game.Extensions;
using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks namespace osu.Game.Rulesets.Edit.Checks
@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks
foreach (var file in beatmapSet.Files) foreach (var file in beatmapSet.Files)
{ {
using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.StoragePath)) using (Stream data = context.WorkingBeatmap.GetStream(file.FileInfo.GetStoragePath()))
{ {
if (data?.Length == 0) if (data?.Length == 0)
yield return new IssueTemplateZeroBytes(this).Create(file.Filename); yield return new IssueTemplateZeroBytes(this).Create(file.Filename);

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring.Legacy; using osu.Game.Scoring.Legacy;
@ -16,7 +17,7 @@ namespace osu.Game.Scoring
{ {
ScoreInfo = score; ScoreInfo = score;
string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; string replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.GetStoragePath();
if (replayFilename == null) if (replayFilename == null)
return; return;

View File

@ -162,7 +162,12 @@ namespace osu.Game.Scoring
// We can compute the max combo locally after the async beatmap difficulty computation. // We can compute the max combo locally after the async beatmap difficulty computation.
var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
beatmapMaxCombo = difficulty.MaxCombo;
// Something failed during difficulty calculation. Fall back to provided score.
if (difficulty == null)
return score.TotalScore;
beatmapMaxCombo = difficulty.Value.MaxCombo;
} }
} }
else else

View File

@ -13,6 +13,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring.Legacy; using osu.Game.Scoring.Legacy;
@ -77,7 +78,7 @@ namespace osu.Game.Scoring
if (file == null) if (file == null)
return; return;
using (var inputStream = Files.Storage.GetStream(file.FileInfo.StoragePath)) using (var inputStream = Files.Storage.GetStream(file.FileInfo.GetStoragePath()))
inputStream.CopyTo(outputStream); inputStream.CopyTo(outputStream);
} }

View File

@ -37,12 +37,12 @@ namespace osu.Game.Scoring
var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false); var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false);
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
if (attributes.Attributes == null) if (attributes?.Attributes == null)
return null; return null;
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Attributes, score); var calculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator(attributes.Value.Attributes, score);
return calculator?.Calculate(); return calculator?.Calculate();
} }

View File

@ -216,7 +216,7 @@ namespace osu.Game.Screens.Play.HUD
this.gameplayBeatmap = gameplayBeatmap; this.gameplayBeatmap = gameplayBeatmap;
} }
public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods = null, CancellationToken? cancellationToken = null) public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods, CancellationToken cancellationToken)
=> gameplayBeatmap; => gameplayBeatmap;
protected override IBeatmap GetBeatmap() => gameplayBeatmap; protected override IBeatmap GetBeatmap() => gameplayBeatmap;

View File

@ -143,14 +143,6 @@ namespace osu.Game.Screens.Ranking.Expanded
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5, 0), Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
new StarRatingDisplay(starDifficulty)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
}
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -231,6 +223,15 @@ namespace osu.Game.Screens.Ranking.Expanded
if (score.Date != default) if (score.Date != default)
AddInternal(new PlayedOnText(score.Date)); AddInternal(new PlayedOnText(score.Date));
if (starDifficulty != null)
{
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
});
}
if (score.Mods.Any()) if (score.Mods.Any())
{ {
starAndModDisplay.Add(new ModDisplay starAndModDisplay.Add(new ModDisplay

View File

@ -152,7 +152,10 @@ namespace osu.Game.Screens.Select.Details
Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() =>
{ {
starDifficulty.Value = ((float)normalStarDifficulty.Result.Stars, (float)moddedStarDifficulty.Result.Stars); if (normalStarDifficulty.Result == null || moddedStarDifficulty.Result == null)
return;
starDifficulty.Value = ((float)normalStarDifficulty.Result.Value.Stars, (float)moddedStarDifficulty.Result.Value.Stars);
}), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
} }

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
@ -35,7 +36,7 @@ namespace osu.Game.Skinning
} }
private string getPathForFile(string filename) => private string getPathForFile(string filename) =>
source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath();
public override IEnumerable<string> GetAvailableResources() => source.Files.Select(f => f.Filename); public override IEnumerable<string> GetAvailableResources() => source.Files.Select(f => f.Filename);
} }

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
@ -63,7 +64,7 @@ namespace osu.Game.Skinning
if (fileInfo == null) if (fileInfo == null)
continue; continue;
byte[] bytes = resources?.Files.Get(fileInfo.FileInfo.StoragePath); byte[] bytes = resources?.Files.Get(fileInfo.FileInfo.GetStoragePath());
if (bytes == null) if (bytes == null)
continue; continue;
@ -93,7 +94,7 @@ namespace osu.Game.Skinning
private Stream getConfigurationStream() private Stream getConfigurationStream()
{ {
string path = SkinInfo.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; string path = SkinInfo.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath();
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
return null; return null;

View File

@ -215,7 +215,7 @@ namespace osu.Game.Skinning
{ {
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{ {
using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath)) using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.GetStoragePath()))
using (var sr = new StreamReader(existingStream)) using (var sr = new StreamReader(existingStream))
{ {
string line; string line;

View File

@ -18,6 +18,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Models; using osu.Game.Models;
@ -197,7 +198,7 @@ namespace osu.Game.Stores
foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase))) foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)))
{ {
using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.StoragePath))) // we need a memory stream so we can seek using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.GetStoragePath()))) // we need a memory stream so we can seek
{ {
IBeatmap decoded; IBeatmap decoded;
using (var lineReader = new LineBufferedReader(memoryStream, true)) using (var lineReader = new LineBufferedReader(memoryStream, true))

View File

@ -15,6 +15,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -305,7 +306,7 @@ namespace osu.Game.Stores
foreach (RealmNamedFileUsage file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename)) foreach (RealmNamedFileUsage file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
{ {
using (Stream s = Files.Store.GetStream(file.File.StoragePath)) using (Stream s = Files.Store.GetStream(file.File.GetStoragePath()))
s.CopyTo(hashable); s.CopyTo(hashable);
} }

View File

@ -10,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Models; using osu.Game.Models;
using Realms; using Realms;
@ -64,7 +65,7 @@ namespace osu.Game.Stores
{ {
data.Seek(0, SeekOrigin.Begin); data.Seek(0, SeekOrigin.Begin);
using (var output = Storage.GetStream(file.StoragePath, FileAccess.Write)) using (var output = Storage.GetStream(file.GetStoragePath(), FileAccess.Write))
data.CopyTo(output); data.CopyTo(output);
data.Seek(0, SeekOrigin.Begin); data.Seek(0, SeekOrigin.Begin);
@ -72,7 +73,7 @@ namespace osu.Game.Stores
private bool checkFileExistsAndMatchesHash(RealmFile file) private bool checkFileExistsAndMatchesHash(RealmFile file)
{ {
string path = file.StoragePath; string path = file.GetStoragePath();
// we may be re-adding a file to fix missing store entries. // we may be re-adding a file to fix missing store entries.
if (!Storage.Exists(path)) if (!Storage.Exists(path))
@ -100,7 +101,7 @@ namespace osu.Game.Stores
try try
{ {
Storage.Delete(file.StoragePath); Storage.Delete(file.GetStoragePath());
realm.Remove(file); realm.Remove(file);
} }
catch (Exception e) catch (Exception e)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -9,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video; using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
namespace osu.Game.Storyboards.Drawables namespace osu.Game.Storyboards.Drawables
{ {
@ -29,7 +31,7 @@ namespace osu.Game.Storyboards.Drawables
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore) private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
{ {
string path = beatmap.Value.BeatmapSetInfo?.Files.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; string path = beatmap.Value.BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.File.GetStoragePath();
if (path == null) if (path == null)
return; return;

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
@ -95,7 +96,7 @@ namespace osu.Game.Storyboards
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
{ {
Drawable drawable = null; Drawable drawable = null;
string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.GetStoragePath();
if (!string.IsNullOrEmpty(storyboardPath)) if (!string.IsNullOrEmpty(storyboardPath))
drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; drawable = new Sprite { Texture = textureStore.Get(storyboardPath) };