mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 12:35:34 +08:00
Split out WorkingBeatmapCache
from BeatmapManager
This commit is contained in:
parent
8a6501fa58
commit
e7e0473323
@ -9,18 +9,13 @@ using System.Linq.Expressions;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Lists;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Statistics;
|
|
||||||
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;
|
||||||
@ -31,7 +26,6 @@ using osu.Game.Online.API.Requests;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Users;
|
|
||||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -40,7 +34,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ExcludeFromDynamicCompile]
|
[ExcludeFromDynamicCompile]
|
||||||
public partial class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>, IBeatmapResourceProvider
|
public class BeatmapManager : DownloadableArchiveModelManager<BeatmapSetInfo, BeatmapSetFileInfo>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when a single difficulty has been hidden.
|
/// Fired when a single difficulty has been hidden.
|
||||||
@ -60,12 +54,12 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<BeatmapSetInfo, CancellationToken, Task> PopulateOnlineInformation;
|
public Func<BeatmapSetInfo, CancellationToken, Task> PopulateOnlineInformation;
|
||||||
|
|
||||||
private readonly Bindable<WeakReference<BeatmapInfo>> beatmapRestored = new Bindable<WeakReference<BeatmapInfo>>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
/// The game working beatmap cache, used to invalidate entries on changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly WorkingBeatmap DefaultBeatmap;
|
public WorkingBeatmapCache WorkingBeatmapCache { private get; set; }
|
||||||
|
|
||||||
|
private readonly Bindable<WeakReference<BeatmapInfo>> beatmapRestored = new Bindable<WeakReference<BeatmapInfo>>();
|
||||||
|
|
||||||
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
|
public override IEnumerable<string> HandledExtensions => new[] { ".osz" };
|
||||||
|
|
||||||
@ -75,35 +69,19 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage();
|
protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage();
|
||||||
|
|
||||||
private readonly RulesetStore rulesets;
|
|
||||||
private readonly BeatmapStore beatmaps;
|
private readonly BeatmapStore beatmaps;
|
||||||
private readonly AudioManager audioManager;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly IResourceStore<byte[]> resources;
|
|
||||||
private readonly LargeTextureStore largeTextureStore;
|
|
||||||
private readonly ITrackStore trackStore;
|
|
||||||
|
|
||||||
[CanBeNull]
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host = null)
|
||||||
private readonly GameHost host;
|
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null,
|
|
||||||
WorkingBeatmap defaultBeatmap = null)
|
|
||||||
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||||
{
|
{
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.audioManager = audioManager;
|
|
||||||
this.resources = resources;
|
|
||||||
this.host = host;
|
|
||||||
|
|
||||||
DefaultBeatmap = defaultBeatmap;
|
|
||||||
|
|
||||||
beatmaps = (BeatmapStore)ModelStore;
|
beatmaps = (BeatmapStore)ModelStore;
|
||||||
beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference<BeatmapInfo>(b);
|
beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference<BeatmapInfo>(b);
|
||||||
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
|
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
|
||||||
beatmaps.ItemRemoved += removeWorkingCache;
|
beatmaps.ItemRemoved += b => WorkingBeatmapCache?.Invalidate(b);
|
||||||
beatmaps.ItemUpdated += removeWorkingCache;
|
beatmaps.ItemUpdated += obj => WorkingBeatmapCache?.Invalidate(obj);
|
||||||
|
|
||||||
largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
|
|
||||||
trackStore = audioManager.GetTrackStore(Files.Store);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
|
||||||
@ -111,33 +89,6 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
|
||||||
|
|
||||||
public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user)
|
|
||||||
{
|
|
||||||
var metadata = new BeatmapMetadata
|
|
||||||
{
|
|
||||||
Author = user,
|
|
||||||
};
|
|
||||||
|
|
||||||
var set = new BeatmapSetInfo
|
|
||||||
{
|
|
||||||
Metadata = metadata,
|
|
||||||
Beatmaps = new List<BeatmapInfo>
|
|
||||||
{
|
|
||||||
new BeatmapInfo
|
|
||||||
{
|
|
||||||
BaseDifficulty = new BeatmapDifficulty(),
|
|
||||||
Ruleset = ruleset,
|
|
||||||
Metadata = metadata,
|
|
||||||
WidescreenStoryboard = true,
|
|
||||||
SamplesMatchPlaybackRate = true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var working = Import(set).Result;
|
|
||||||
return GetWorkingBeatmap(working.Beatmaps.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
@ -278,43 +229,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeWorkingCache(info);
|
WorkingBeatmapCache?.Invalidate(info);
|
||||||
}
|
|
||||||
|
|
||||||
private readonly WeakList<BeatmapManagerWorkingBeatmap> workingCache = new WeakList<BeatmapManagerWorkingBeatmap>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="beatmapInfo">The beatmap to lookup.</param>
|
|
||||||
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
|
|
||||||
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 = 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -515,35 +430,6 @@ namespace osu.Game.Beatmaps
|
|||||||
return endTime - startTime;
|
return endTime - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeWorkingCache(BeatmapSetInfo info)
|
|
||||||
{
|
|
||||||
if (info.Beatmaps == null) return;
|
|
||||||
|
|
||||||
foreach (var b in info.Beatmaps)
|
|
||||||
removeWorkingCache(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeWorkingCache(BeatmapInfo info)
|
|
||||||
{
|
|
||||||
lock (workingCache)
|
|
||||||
{
|
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
|
||||||
if (working != null)
|
|
||||||
workingCache.Remove(working);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IResourceStorageProvider
|
|
||||||
|
|
||||||
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
|
|
||||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
|
||||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Files => Files.Store;
|
|
||||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
|
||||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
// 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.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Logging;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
public partial class BeatmapManager
|
|
||||||
{
|
|
||||||
[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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
279
osu.Game/Beatmaps/WorkingBeatmapCache.cs
Normal file
279
osu.Game/Beatmaps/WorkingBeatmapCache.cs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// 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 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.Rulesets;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps
|
||||||
|
{
|
||||||
|
public class WorkingBeatmapCache : IBeatmapResourceProvider
|
||||||
|
{
|
||||||
|
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 BeatmapManager 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([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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="WorkingBeatmap"/>.
|
||||||
|
/// </summary>
|
||||||
|
public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user)
|
||||||
|
{
|
||||||
|
var metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Author = user,
|
||||||
|
};
|
||||||
|
|
||||||
|
var set = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Metadata = metadata,
|
||||||
|
Beatmaps = new List<BeatmapInfo>
|
||||||
|
{
|
||||||
|
new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty(),
|
||||||
|
Ruleset = ruleset,
|
||||||
|
Metadata = metadata,
|
||||||
|
WidescreenStoryboard = true,
|
||||||
|
SamplesMatchPlaybackRate = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var working = BeatmapManager.Import(set).Result;
|
||||||
|
return GetWorkingBeatmap(working.Beatmaps.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmapInfo">The beatmap to lookup.</param>
|
||||||
|
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user