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 Cache = []; // In-flight requests static readonly Dictionary> 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 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 requests, AudioType audioType, string fileName, Action 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(); } }