using System; using System.Collections.Generic; using System.Linq; using Framework; using PhxhSDK; /// /// 音频停止池 - 使用 LRU 算法管理停止的 AudioPlayer /// - 按 key 索引,提高复用率 /// - 分类型限制,防止内存泄漏 /// - LRU 淘汰,自动清理长期不用的 AudioPlayer /// public class AudioStoppedPool { #region Fields // 按 key 索引(用于快速查找匹配的 AudioPlayer) // key 格式: "音频类型_配置key",如 "Music_bgm_01" private Dictionary> _poolByKey; // 按 ID 索引(用于快速查找和移除) private Dictionary _poolById; // 当前各类型的数量 private Dictionary _currentCount; // 各类型的上限 private Dictionary _maxLimit; #endregion #region Init public AudioStoppedPool() { _poolByKey = new Dictionary>(); _poolById = new Dictionary(); _currentCount = new Dictionary { { EAudioType.Music, 0 }, { EAudioType.Sound, 0 }, { EAudioType.Speak, 0 } }; _maxLimit = new Dictionary { { EAudioType.Music, 5 }, // Music 停止池上限 5 { EAudioType.Sound, 50 }, // Sound 停止池上限 50 { EAudioType.Speak, 3 } // Speak 停止池上限 3 }; } #endregion #region Public Methods /// /// 设置停止池上限 /// public void SetLimit(EAudioType type, int limit) { _maxLimit[type] = limit; } /// /// 添加到停止池(带 LRU 淘汰) /// /// 被淘汰的 AudioPlayer 列表 public List AddStopped(EAudioType type, string key, AudioPlayer audio) { if (audio == null || string.IsNullOrEmpty(key)) return new List(); var poolKey = MakePoolKey(type, key); // 1. 添加到 key 索引 if (!_poolByKey.TryGetValue(poolKey, out var list)) { list = new LinkedList(); _poolByKey[poolKey] = list; } list.AddFirst(audio); // 添加到链表头部(最近使用) // 2. 添加到 ID 索引 _poolById[audio.AudioId] = audio; _currentCount[type]++; // 3. 检查是否超过上限,执行 LRU 淘汰 var toDestroy = new List(); if (_currentCount[type] > _maxLimit[type]) { int excessCount = _currentCount[type] - _maxLimit[type]; toDestroy = EvictOldest(type, excessCount); } return toDestroy; } /// /// 获取空闲 AudioPlayer(优先匹配 key) /// public AudioPlayer GetFree(EAudioType type, string key) { if (string.IsNullOrEmpty(key)) return null; var poolKey = MakePoolKey(type, key); // 1. 精确匹配(同类型 + 同 key) if (_poolByKey.TryGetValue(poolKey, out var list) && list.Count > 0) { var audio = list.First.Value; RemoveFromPool(audio); return audio; } // 2. Fallback: 同类型,任意 key(从最近使用的开始) var typePrefix = $"{type}_"; foreach (var kv in _poolByKey) { if (kv.Key.StartsWith(typePrefix) && kv.Value.Count > 0) { var audio = kv.Value.First.Value; RemoveFromPool(audio); return audio; } } return null; } /// /// 通过 ID 查询停止池中的 AudioPlayer /// public AudioPlayer GetById(int id) { return _poolById.TryGetValue(id, out var audio) ? audio : null; } /// /// 通过 ID 从停止池移除 /// public AudioPlayer RemoveById(int id) { if (!_poolById.TryGetValue(id, out var audio)) return null; RemoveFromPool(audio); return audio; } /// /// 获取当前统计信息 /// public (int music, int sound, int speak) GetStats() { return (_currentCount[EAudioType.Music], _currentCount[EAudioType.Sound], _currentCount[EAudioType.Speak]); } /// /// 清空停止池 /// public void Clear() { _poolByKey.Clear(); _poolById.Clear(); _currentCount[EAudioType.Music] = 0; _currentCount[EAudioType.Sound] = 0; _currentCount[EAudioType.Speak] = 0; } /// /// 释放所有 AudioPlayer 资源并清空停止池 /// 适用于场景卸载、关卡退出等需要彻底清理的场景 /// /// 释放的 AudioPlayer 数量 public int DisposeAll() { int count = 0; // 遍历所有 key 的音频列表 foreach (var list in _poolByKey.Values) { // 释放每个 AudioPlayer foreach (var audio in list) { audio.Dispose(); count++; } } // 清空池子 _poolByKey.Clear(); _poolById.Clear(); _currentCount[EAudioType.Music] = 0; _currentCount[EAudioType.Sound] = 0; _currentCount[EAudioType.Speak] = 0; return count; } #endregion #region Private Methods /// /// 从停止池移除 AudioPlayer /// private void RemoveFromPool(AudioPlayer audio) { if (audio == null) return; // 从 ID 索引移除 _poolById.Remove(audio.AudioId); // 从 key 索引移除 var poolKey = MakePoolKey(audio.AudioType, audio.CurrentPlayingKey); if (_poolByKey.TryGetValue(poolKey, out var list)) { list.Remove(audio); if (list.Count == 0) { _poolByKey.Remove(poolKey); } } // 更新计数 _currentCount[audio.AudioType]--; } /// /// LRU 淘汰:移除最久未使用的 N 个 AudioPlayer /// private List EvictOldest(EAudioType type, int count) { var toDestroy = new List(); var typePrefix = $"{type}_"; // 收集所有该类型的 AudioPlayer(从链表尾部开始,即最久未使用的) var candidates = new List(); foreach (var kv in _poolByKey) { if (kv.Key.StartsWith(typePrefix)) { // 从链表尾部开始收集(最久未使用) var node = kv.Value.Last; while (node != null && candidates.Count < count) { candidates.Add(node.Value); node = node.Previous; } if (candidates.Count >= count) break; } } // 移除并返回 foreach (var audio in candidates) { RemoveFromPool(audio); toDestroy.Add(audio); } return toDestroy; } /// /// 生成池 key /// private string MakePoolKey(EAudioType type, string key) { return $"{type}_{key}"; } #endregion }