From 3c95497ed78fd2b8a0b38cf682ceb3ebfbfa2eba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Dec 2021 18:01:09 +0900 Subject: [PATCH] Reorder methods to hopefully make more sense --- osu.Game/Beatmaps/WorkingBeatmap.cs | 340 ++++++++++++++++------------ 1 file changed, 189 insertions(+), 151 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 2baac55c60..bdce60636b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -51,6 +51,110 @@ namespace osu.Game.Beatmaps skin = new RecyclableLazy(GetSkin); } + #region Load checks + + public virtual bool TrackLoaded => loadedTrack != null; + public bool WaveformLoaded => waveform.IsResultAvailable; + public bool StoryboardLoaded => storyboard.IsResultAvailable; + public bool SkinLoaded => skin.IsResultAvailable; + public bool BackgroundLoaded => background.IsResultAvailable; + public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + + #endregion + + #region Resource getters + + protected virtual Waveform GetWaveform() => new Waveform(null); + protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; + + protected abstract IBeatmap GetBeatmap(); + protected abstract Texture GetBackground(); + protected abstract Track GetBeatmapTrack(); + + /// + /// Creates a new skin instance for this beatmap. + /// + /// + /// This should only be called externally in scenarios where it is explicitly desired to get a new instance of a skin + /// (e.g. for editing purposes, to avoid state pollution). + /// For standard reading purposes, should always be used directly. + /// + protected internal abstract ISkin GetSkin(); + + #endregion + + #region Async load control + + public void BeginAsyncLoad() => loadBeatmapAsync(); + + public void CancelAsyncLoad() + { + lock (beatmapFetchLock) + { + loadCancellationSource?.Cancel(); + loadCancellationSource = new CancellationTokenSource(); + + if (beatmapLoadTask?.IsCompleted != true) + beatmapLoadTask = null; + } + } + + #endregion + + #region Background + + public Texture Background => background.Value; + private readonly RecyclableLazy background; + + protected virtual bool BackgroundStillValid(Texture b) => b == null || b.Available; + + #endregion + + #region Track + + private Track loadedTrack; + + public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); + + public void PrepareTrackForPreviewLooping() + { + Track.Looping = true; + Track.RestartPoint = Metadata.PreviewTime; + + if (Track.RestartPoint == -1) + { + if (!Track.IsLoaded) + { + // force length to be populated (https://github.com/ppy/osu-framework/issues/4202) + Track.Seek(Track.CurrentTime); + } + + Track.RestartPoint = 0.4f * Track.Length; + } + } + + /// + /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap + /// across difficulties in the same beatmap set. + /// + /// The track to transfer. + public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track)); + + /// + /// Get the loaded audio track instance. must have first been called. + /// This generally happens via MusicController when changing the global beatmap. + /// + public Track Track + { + get + { + if (!TrackLoaded) + throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}."); + + return loadedTrack; + } + } + protected Track GetVirtualTrack(double emptyLength = 0) { const double excess_length = 1000; @@ -77,13 +181,82 @@ namespace osu.Game.Beatmaps return audioManager.Tracks.GetVirtual(length); } - /// - /// Creates a to convert a for a specified . - /// - /// The to be converted. - /// The for which should be converted. - /// The applicable . - protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); + #endregion + + #region Waveform + + public Waveform Waveform => waveform.Value; + private readonly RecyclableLazy waveform; + + #endregion + + #region Storyboard + + public Storyboard Storyboard => storyboard.Value; + private readonly RecyclableLazy storyboard; + + #endregion + + #region Skin + + private readonly RecyclableLazy skin; + + public ISkin Skin => skin.Value; + + #endregion + + #region Beatmap + + public IBeatmap Beatmap + { + get + { + try + { + return loadBeatmapAsync().Result; + } + catch (AggregateException ae) + { + // This is the exception that is generally expected here, which occurs via natural cancellation of the asynchronous load + if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException) + return null; + + Logger.Error(ae, "Beatmap failed to load"); + return null; + } + catch (Exception e) + { + Logger.Error(e, "Beatmap failed to load"); + return null; + } + } + } + + private Task beatmapLoadTask; + + private Task loadBeatmapAsync() + { + lock (beatmapFetchLock) + { + return beatmapLoadTask ??= Task.Factory.StartNew(() => + { + // Todo: Handle cancellation during beatmap parsing + var b = GetBeatmap() ?? new Beatmap(); + + // The original beatmap version needs to be preserved as the database doesn't contain it + BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion; + + // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc) + b.BeatmapInfo = BeatmapInfo; + + return b; + }, loadCancellationSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + } + + #endregion + + #region Playable beatmap public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods = null) { @@ -101,7 +274,7 @@ namespace osu.Game.Beatmaps } } - public virtual IBeatmap GetPlayableBeatmap([NotNull] IRulesetInfo ruleset, [NotNull] IReadOnlyList mods, CancellationToken token) + public virtual IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken token) { var rulesetInstance = ruleset.CreateInstance(); @@ -175,155 +348,20 @@ namespace osu.Game.Beatmaps return converted; } - public void BeginAsyncLoad() => loadBeatmapAsync(); + /// + /// Creates a to convert a for a specified . + /// + /// The to be converted. + /// The for which should be converted. + /// The applicable . + protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public void CancelAsyncLoad() - { - lock (beatmapFetchLock) - { - loadCancellationSource?.Cancel(); - loadCancellationSource = new CancellationTokenSource(); - - if (beatmapLoadTask?.IsCompleted != true) - beatmapLoadTask = null; - } - } - - private Task loadBeatmapAsync() - { - lock (beatmapFetchLock) - { - return beatmapLoadTask ??= Task.Factory.StartNew(() => - { - // Todo: Handle cancellation during beatmap parsing - var b = GetBeatmap() ?? new Beatmap(); - - // The original beatmap version needs to be preserved as the database doesn't contain it - BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion; - - // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc) - b.BeatmapInfo = BeatmapInfo; - - return b; - }, loadCancellationSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); - } - } + #endregion public override string ToString() => BeatmapInfo.ToString(); - public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; - IBeatmapInfo IWorkingBeatmap.BeatmapInfo => BeatmapInfo; - public IBeatmap Beatmap - { - get - { - try - { - return loadBeatmapAsync().Result; - } - catch (AggregateException ae) - { - // This is the exception that is generally expected here, which occurs via natural cancellation of the asynchronous load - if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException) - return null; - - Logger.Error(ae, "Beatmap failed to load"); - return null; - } - catch (Exception e) - { - Logger.Error(e, "Beatmap failed to load"); - return null; - } - } - } - - protected abstract IBeatmap GetBeatmap(); - private Task beatmapLoadTask; - - public bool BackgroundLoaded => background.IsResultAvailable; - public Texture Background => background.Value; - protected virtual bool BackgroundStillValid(Texture b) => b == null || b.Available; - protected abstract Texture GetBackground(); - private readonly RecyclableLazy background; - - private Track loadedTrack; - - public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); - - public void PrepareTrackForPreviewLooping() - { - Track.Looping = true; - Track.RestartPoint = Metadata.PreviewTime; - - if (Track.RestartPoint == -1) - { - if (!Track.IsLoaded) - { - // force length to be populated (https://github.com/ppy/osu-framework/issues/4202) - Track.Seek(Track.CurrentTime); - } - - Track.RestartPoint = 0.4f * Track.Length; - } - } - - /// - /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap - /// across difficulties in the same beatmap set. - /// - /// The track to transfer. - public void TransferTrack([NotNull] Track track) => loadedTrack = track ?? throw new ArgumentNullException(nameof(track)); - - /// - /// Whether this beatmap's track has been loaded via . - /// - public virtual bool TrackLoaded => loadedTrack != null; - - /// - /// Get the loaded audio track instance. must have first been called. - /// This generally happens via MusicController when changing the global beatmap. - /// - public Track Track - { - get - { - if (!TrackLoaded) - throw new InvalidOperationException($"Cannot access {nameof(Track)} without first calling {nameof(LoadTrack)}."); - - return loadedTrack; - } - } - - protected abstract Track GetBeatmapTrack(); - - public bool WaveformLoaded => waveform.IsResultAvailable; - public Waveform Waveform => waveform.Value; - protected virtual Waveform GetWaveform() => new Waveform(null); - private readonly RecyclableLazy waveform; - - public bool StoryboardLoaded => storyboard.IsResultAvailable; - public Storyboard Storyboard => storyboard.Value; - protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; - private readonly RecyclableLazy storyboard; - - public bool SkinLoaded => skin.IsResultAvailable; - public ISkin Skin => skin.Value; - - /// - /// Creates a new skin instance for this beatmap. - /// - /// - /// This should only be called externally in scenarios where it is explicitly desired to get a new instance of a skin - /// (e.g. for editing purposes, to avoid state pollution). - /// For standard reading purposes, should always be used directly. - /// - protected internal abstract ISkin GetSkin(); - - private readonly RecyclableLazy skin; - public abstract Stream GetStream(string storagePath); public class RecyclableLazy