diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 5f1f0d1e40..61bd962648 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . 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.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -60,8 +61,9 @@ namespace osu.Game.Beatmaps /// /// The to create a playable for. /// The s to apply to the . + /// The loading timeout. /// The converted . /// If could not be converted to . - IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null); + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null); } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1e1ffad81e..bdcfc058b4 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -83,55 +83,73 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) { - mods ??= Array.Empty(); - - var rulesetInstance = ruleset.CreateInstance(); - - IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); - - // Check if the beatmap can be converted - if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert()) - throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); - - // Apply conversion mods - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmapConverter(converter); - - // Convert - IBeatmap converted = converter.Convert(); - - // Apply difficulty mods - if (mods.Any(m => m is IApplicableToDifficulty)) + using (var cancellationSource = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10))) { - converted.BeatmapInfo = converted.BeatmapInfo.Clone(); - converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); + mods ??= Array.Empty(); - foreach (var mod in mods.OfType()) - mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); - } + var rulesetInstance = ruleset.CreateInstance(); - IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); - processor?.PreProcess(); + // Check if the beatmap can be converted + if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert()) + throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); - // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed - foreach (var obj in converted.HitObjects) - obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); + // Apply conversion mods + foreach (var mod in mods.OfType()) + { + cancellationSource.Token.ThrowIfCancellationRequested(); + mod.ApplyToBeatmapConverter(converter); + } - foreach (var mod in mods.OfType()) - { + // Convert + IBeatmap converted = converter.Convert(); + + // Apply difficulty mods + if (mods.Any(m => m is IApplicableToDifficulty)) + { + converted.BeatmapInfo = converted.BeatmapInfo.Clone(); + converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); + + foreach (var mod in mods.OfType()) + { + cancellationSource.Token.ThrowIfCancellationRequested(); + mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); + } + } + + IBeatmapProcessor processor = rulesetInstance.CreateBeatmapProcessor(converted); + + processor?.PreProcess(); + + // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed foreach (var obj in converted.HitObjects) - mod.ApplyToHitObject(obj); + { + cancellationSource.Token.ThrowIfCancellationRequested(); + obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); + } + + foreach (var mod in mods.OfType()) + { + foreach (var obj in converted.HitObjects) + { + cancellationSource.Token.ThrowIfCancellationRequested(); + mod.ApplyToHitObject(obj); + } + } + + processor?.PostProcess(); + + foreach (var mod in mods.OfType()) + { + cancellationSource.Token.ThrowIfCancellationRequested(); + mod.ApplyToBeatmap(converted); + } + + return converted; } - - processor?.PostProcess(); - - foreach (var mod in mods.OfType()) - mod.ApplyToBeatmap(converted); - - return converted; } private CancellationTokenSource loadCancellation = new CancellationTokenSource();