NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/Audio/AudioStoppedPool.cs

283 lines
7.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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