osu/osu.Game/Beatmaps/WorkingBeatmapCache.cs

247 lines
8.9 KiB
C#
Raw Normal View History

// 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.
2018-04-13 09:19:50 +00:00
using System;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Audio;
2018-04-13 09:19:50 +00:00
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Lists;
2018-04-13 09:19:50 +00:00
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
using osu.Framework.Testing;
2018-04-13 09:19:50 +00:00
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
using osu.Game.IO;
2018-04-13 09:19:50 +00:00
using osu.Game.Skinning;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
public class WorkingBeatmapCache : IBeatmapResourceProvider, IWorkingBeatmapCache
2018-04-13 09:19:50 +00:00
{
private readonly WeakList<BeatmapManagerWorkingBeatmap> workingCache = new WeakList<BeatmapManagerWorkingBeatmap>();
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
public readonly WorkingBeatmap DefaultBeatmap;
public BeatmapModelManager BeatmapManager { private get; set; }
private readonly AudioManager audioManager;
private readonly IResourceStore<byte[]> resources;
private readonly LargeTextureStore largeTextureStore;
private readonly ITrackStore trackStore;
private readonly IResourceStore<byte[]> files;
[CanBeNull]
private readonly GameHost host;
public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> files, WorkingBeatmap defaultBeatmap = null, GameHost host = null)
{
DefaultBeatmap = defaultBeatmap;
this.audioManager = audioManager;
this.resources = resources;
this.host = host;
this.files = files;
largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(files));
this.trackStore = trackStore;
}
public void Invalidate(BeatmapSetInfo info)
{
foreach (var b in info.Beatmaps)
Invalidate(b);
}
public void Invalidate(BeatmapInfo info)
{
lock (workingCache)
{
var working = workingCache.FirstOrDefault(w => info.Equals(w.BeatmapInfo));
if (working != null)
{
Logger.Log($"Invalidating working beatmap cache for {info}");
workingCache.Remove(working);
}
}
}
public virtual WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo)
{
// 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;
beatmapInfo = BeatmapManager.QueryBeatmap(b => b.ID == lookupId);
}
if (beatmapInfo?.BeatmapSet == null)
return DefaultBeatmap;
lock (workingCache)
{
var working = workingCache.FirstOrDefault(w => beatmapInfo.Equals(w.BeatmapInfo));
if (working != null)
return working;
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
// best effort; may be higher than expected.
GlobalStatistics.Get<int>(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count();
return working;
}
}
#region IResourceStorageProvider
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
AudioManager IStorageResourceProvider.AudioManager => audioManager;
RealmContextFactory IStorageResourceProvider.RealmContextFactory => null;
IResourceStore<byte[]> IStorageResourceProvider.Files => files;
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
#endregion
[ExcludeFromDynamicCompile]
2020-08-11 04:48:57 +00:00
private class BeatmapManagerWorkingBeatmap : WorkingBeatmap
2018-04-13 09:19:50 +00:00
{
[NotNull]
private readonly IBeatmapResourceProvider resources;
2018-04-13 09:19:50 +00:00
public BeatmapManagerWorkingBeatmap(BeatmapInfo beatmapInfo, [NotNull] IBeatmapResourceProvider resources)
: base(beatmapInfo, resources.AudioManager)
2018-04-13 09:19:50 +00:00
{
this.resources = resources;
2018-04-13 09:19:50 +00:00
}
2018-04-19 11:44:38 +00:00
protected override IBeatmap GetBeatmap()
2018-04-13 09:19:50 +00:00
{
if (BeatmapInfo.Path == null)
2020-09-04 04:13:53 +00:00
return new Beatmap { BeatmapInfo = BeatmapInfo };
2018-04-13 09:19:50 +00:00
try
{
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
2018-04-13 09:19:50 +00:00
return Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
}
2020-02-10 08:25:11 +00:00
catch (Exception e)
2018-04-13 09:19:50 +00:00
{
2020-02-10 08:25:11 +00:00
Logger.Error(e, "Beatmap failed to load");
2018-04-13 09:19:50 +00:00
return null;
}
}
protected override Texture GetBackground()
{
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
2018-04-13 09:19:50 +00:00
return null;
try
{
return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile));
2018-04-13 09:19:50 +00:00
}
2020-02-10 08:25:11 +00:00
catch (Exception e)
2018-04-13 09:19:50 +00:00
{
2020-02-10 08:25:11 +00:00
Logger.Error(e, "Background failed to load");
2018-04-13 09:19:50 +00:00
return null;
}
}
2019-08-30 20:19:34 +00:00
2020-08-07 13:31:41 +00:00
protected override Track GetBeatmapTrack()
2018-04-13 09:19:50 +00:00
{
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
2018-04-13 09:19:50 +00:00
try
{
return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
2018-04-13 09:19:50 +00:00
}
2020-02-10 08:25:11 +00:00
catch (Exception e)
2018-04-13 09:19:50 +00:00
{
2020-02-10 08:25:11 +00:00
Logger.Error(e, "Track failed to load");
return null;
2018-04-13 09:19:50 +00:00
}
}
protected override Waveform GetWaveform()
{
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
try
{
var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData);
}
2020-02-10 08:25:11 +00:00
catch (Exception e)
{
2020-02-10 08:25:11 +00:00
Logger.Error(e, "Waveform failed to load");
return null;
}
}
2018-04-13 09:19:50 +00:00
protected override Storyboard GetStoryboard()
{
Storyboard storyboard;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
try
{
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
2018-04-13 09:19:50 +00:00
{
var decoder = Decoder.GetDecoder<Storyboard>(stream);
string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
2018-04-13 09:19:50 +00:00
// todo: support loading from both set-wide storyboard *and* beatmap specific.
if (string.IsNullOrEmpty(storyboardFilename))
2018-04-13 09:19:50 +00:00
storyboard = decoder.Decode(stream);
else
{
using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename))))
2018-04-13 09:19:50 +00:00
storyboard = decoder.Decode(stream, secondaryStream);
}
}
}
catch (Exception e)
{
Logger.Error(e, "Storyboard failed to load");
storyboard = new Storyboard();
}
storyboard.BeatmapInfo = BeatmapInfo;
return storyboard;
}
protected internal override ISkin GetSkin()
2018-04-13 09:19:50 +00:00
{
try
{
return new LegacyBeatmapSkin(BeatmapInfo, resources.Files, resources);
2018-04-13 09:19:50 +00:00
}
catch (Exception e)
{
Logger.Error(e, "Skin failed to load");
return null;
2018-04-13 09:19:50 +00:00
}
}
public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath);
2018-04-13 09:19:50 +00:00
}
}
}