1
0
Fork 0
muzika-gromche/MuzikaGromche/AudioClipsCache.cs

171 lines
5.7 KiB
C#

using HarmonyLib;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Networking;
namespace MuzikaGromche;
internal static class AudioClipsCacheManager
{
// Cache of file names to loaded AudioClips.
// Cache is cleared at the end of each round.
static readonly Dictionary<string, AudioClip> Cache = [];
// In-flight requests
static readonly Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> Requests = [];
// Not just isDone status, but also whether all requests have been processed.
public static bool AllDone => Requests.Count == 0;
public static void LoadAudioTrack(IAudioTrack track)
{
GlobalBehaviour.Instance.StartCoroutine(LoadAudioTrackCoroutine(track));
}
static IEnumerator LoadAudioTrackCoroutine(IAudioTrack track)
{
List<UnityWebRequest> requests = [];
requests.Capacity = 2;
LoadAudioClip(requests, track.AudioType, track.FileNameIntro, clip => track.LoadedIntro = clip);
LoadAudioClip(requests, track.AudioType, track.FileNameLoop, clip => track.LoadedLoop = clip);
yield return new WaitUntil(() => requests.All(request => request.isDone));
if (requests.All(request => request.result == UnityWebRequest.Result.Success))
{
foreach (var request in requests)
{
foreach (var (fileName, (Request, Setters)) in Requests)
{
if (request == Request)
{
Plugin.Log.LogDebug($"Audio clip loaded successfully: {fileName}");
var clip = DownloadHandlerAudioClip.GetContent(request);
Cache[fileName] = clip;
foreach (var setter in Setters)
{
setter(clip);
}
}
}
}
}
else
{
var failed = Requests.Values.Where(tuple => tuple.Request.result != UnityWebRequest.Result.Success).Select(tuple => tuple.Request.GetUrl());
Plugin.Log.LogError("Could not load audio file " + string.Join(", ", failed));
}
// cleanup
foreach (var request in requests)
{
// collect matching keys first to avoid mutating Requests while iterating it
var fileNames = Requests
.Where(kv => kv.Value.Request == request)
.Select(kv => kv.Key)
.ToArray();
foreach (var fileName in fileNames)
{
if (Requests.TryGetValue(fileName, out var tuple) && tuple.Request != null)
{
tuple.Request.Dispose();
}
Requests.Remove(fileName);
}
}
}
static readonly string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
static void LoadAudioClip(List<UnityWebRequest> requests, AudioType audioType, string fileName, Action<AudioClip> setter)
{
if (Cache.TryGetValue(fileName, out var cachedClip))
{
Plugin.Log.LogDebug($"Found cached audio clip: {fileName}");
setter(cachedClip);
}
else if (Requests.TryGetValue(fileName, out var tuple))
{
Plugin.Log.LogDebug($"Found existing in-flight request for audio clip: {fileName}");
tuple.Setters.Add(setter);
}
else
{
Plugin.Log.LogDebug($"Sending request to load audio clip: {fileName}");
var request = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{fileName}", audioType);
request.SendWebRequest();
Requests[fileName] = (request, [setter]);
requests.Add(request);
}
}
public static void Clear()
{
// Iterate over LoadedClipsCache keys and values, cross join with Plugin.Tracks list,
// find AudioTracks that reference the key (file name), and null their corresponding loaded tracks;
// then destroy tracks and clear the cache.
Plugin.Log.LogDebug($"Clearing {Cache.Count} cached audio clips and {Requests.Count} pending requests");
if (Cache.Count > 0)
{
var allTracks = Plugin.Tracks.SelectMany(t => t.GetTracks()).ToArray();
foreach (var (fileName, clip) in Cache)
{
foreach (var track in allTracks)
{
// Null out any references to this clip on matching file names.
if (track.FileNameIntro == fileName)
{
track.LoadedIntro = null;
}
if (track.FileNameLoop == fileName)
{
track.LoadedLoop = null;
}
}
if (clip != null)
{
UnityEngine.Object.Destroy(clip);
}
}
Cache.Clear();
}
foreach (var (fileName, (Request, Setters)) in Requests)
{
if (Request != null)
{
Request.Abort();
Request.Dispose();
}
}
Requests.Clear();
}
}
[HarmonyPatch(typeof(RoundManager))]
static class ClearAudioClipCachePatch
{
[HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))]
[HarmonyPatch(nameof(RoundManager.OnDestroy))]
[HarmonyPrefix]
static void OnDestroy(RoundManager __instance)
{
var _ = __instance;
AudioClipsCacheManager.Clear();
}
}