2021-09-30 14:40:41 +08:00
// 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.IO ;
using System.Linq ;
using JetBrains.Annotations ;
using osu.Framework.Audio ;
using osu.Framework.Audio.Track ;
using osu.Framework.Graphics.Textures ;
using osu.Framework.IO.Stores ;
using osu.Framework.Lists ;
using osu.Framework.Logging ;
using osu.Framework.Platform ;
using osu.Framework.Statistics ;
using osu.Framework.Testing ;
using osu.Game.Beatmaps.Formats ;
using osu.Game.IO ;
using osu.Game.Skinning ;
using osu.Game.Storyboards ;
namespace osu.Game.Beatmaps
{
2021-09-30 15:45:32 +08:00
public class WorkingBeatmapCache : IBeatmapResourceProvider , IWorkingBeatmapCache
2021-09-30 14:40:41 +08: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 ;
2021-09-30 15:45:32 +08:00
public BeatmapModelManager BeatmapManager { private get ; set ; }
2021-09-30 14:40:41 +08:00
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 ( [ NotNull ] 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 ) ) ;
trackStore = audioManager . GetTrackStore ( files ) ;
}
public void Invalidate ( BeatmapSetInfo info )
{
if ( info . Beatmaps = = null ) return ;
foreach ( var b in info . Beatmaps )
Invalidate ( b ) ;
}
public void Invalidate ( BeatmapInfo info )
{
lock ( workingCache )
{
var working = workingCache . FirstOrDefault ( w = > w . BeatmapInfo ? . ID = = info . ID ) ;
if ( working ! = null )
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 = > w . BeatmapInfo ? . ID = = beatmapInfo . ID ) ;
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 ;
IResourceStore < byte [ ] > IStorageResourceProvider . Files = > files ;
IResourceStore < byte [ ] > IStorageResourceProvider . Resources = > resources ;
IResourceStore < TextureUpload > IStorageResourceProvider . CreateTextureLoaderStore ( IResourceStore < byte [ ] > underlyingStore ) = > host ? . CreateTextureLoaderStore ( underlyingStore ) ;
#endregion
[ExcludeFromDynamicCompile]
private class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{
[NotNull]
private readonly IBeatmapResourceProvider resources ;
public BeatmapManagerWorkingBeatmap ( BeatmapInfo beatmapInfo , [ NotNull ] IBeatmapResourceProvider resources )
: base ( beatmapInfo , resources . AudioManager )
{
this . resources = resources ;
}
protected override IBeatmap GetBeatmap ( )
{
if ( BeatmapInfo . Path = = null )
return new Beatmap { BeatmapInfo = BeatmapInfo } ;
try
{
using ( var stream = new LineBufferedReader ( GetStream ( BeatmapSetInfo . GetPathForFile ( BeatmapInfo . Path ) ) ) )
return Decoder . GetDecoder < Beatmap > ( stream ) . Decode ( stream ) ;
}
catch ( Exception e )
{
Logger . Error ( e , "Beatmap failed to load" ) ;
return null ;
}
}
protected override bool BackgroundStillValid ( Texture b ) = > false ; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
protected override Texture GetBackground ( )
{
if ( Metadata ? . BackgroundFile = = null )
return null ;
try
{
return resources . LargeTextureStore . Get ( BeatmapSetInfo . GetPathForFile ( Metadata . BackgroundFile ) ) ;
}
catch ( Exception e )
{
Logger . Error ( e , "Background failed to load" ) ;
return null ;
}
}
protected override Track GetBeatmapTrack ( )
{
if ( Metadata ? . AudioFile = = null )
return null ;
try
{
return resources . Tracks . Get ( BeatmapSetInfo . GetPathForFile ( Metadata . AudioFile ) ) ;
}
catch ( Exception e )
{
Logger . Error ( e , "Track failed to load" ) ;
return null ;
}
}
protected override Waveform GetWaveform ( )
{
if ( Metadata ? . AudioFile = = null )
return null ;
try
{
var trackData = GetStream ( BeatmapSetInfo . GetPathForFile ( Metadata . AudioFile ) ) ;
return trackData = = null ? null : new Waveform ( trackData ) ;
}
catch ( Exception e )
{
Logger . Error ( e , "Waveform failed to load" ) ;
return null ;
}
}
protected override Storyboard GetStoryboard ( )
{
Storyboard storyboard ;
try
{
using ( var stream = new LineBufferedReader ( GetStream ( BeatmapSetInfo . GetPathForFile ( BeatmapInfo . Path ) ) ) )
{
var decoder = Decoder . GetDecoder < Storyboard > ( stream ) ;
// todo: support loading from both set-wide storyboard *and* beatmap specific.
if ( BeatmapSetInfo ? . StoryboardFile = = null )
storyboard = decoder . Decode ( stream ) ;
else
{
using ( var secondaryStream = new LineBufferedReader ( GetStream ( BeatmapSetInfo . GetPathForFile ( BeatmapSetInfo . StoryboardFile ) ) ) )
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 ( )
{
try
{
return new LegacyBeatmapSkin ( BeatmapInfo , resources . Files , resources ) ;
}
catch ( Exception e )
{
Logger . Error ( e , "Skin failed to load" ) ;
return null ;
}
}
public override Stream GetStream ( string storagePath ) = > resources . Files . GetStream ( storagePath ) ;
}
}
}