1370 lines
42 KiB
C#
1370 lines
42 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading;
|
||
using AlicizaX;
|
||
using AlicizaX.Resource.Runtime;
|
||
using Cysharp.Threading.Tasks;
|
||
using UnityEngine;
|
||
|
||
|
||
namespace AlicizaX
|
||
{
|
||
/// <summary>
|
||
/// 对象池配置项。
|
||
/// </summary>
|
||
[Serializable]
|
||
public class PoolConfig
|
||
{
|
||
public string asset;
|
||
public float time;
|
||
public int poolCount;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 预制体引用计数信息。
|
||
/// </summary>
|
||
public class PrefabRefInfo
|
||
{
|
||
public GameObject Prefab;
|
||
public int RefCount;
|
||
public float LastAccessTime;
|
||
public string AssetPath;
|
||
|
||
public PrefabRefInfo(GameObject prefab, string assetPath)
|
||
{
|
||
this.Prefab = prefab;
|
||
this.AssetPath = assetPath;
|
||
this.RefCount = 0;
|
||
this.LastAccessTime = Time.time;
|
||
}
|
||
|
||
public void AddRef()
|
||
{
|
||
RefCount++;
|
||
LastAccessTime = Time.time;
|
||
}
|
||
|
||
public void RemoveRef()
|
||
{
|
||
if (RefCount > 0) // 防止引用计数变成负数
|
||
{
|
||
RefCount--;
|
||
if (RefCount > 0)
|
||
{
|
||
LastAccessTime = Time.time;
|
||
}
|
||
|
||
Log.Info($"RemoveRef: {AssetPath}, refCount: {RefCount}");
|
||
}
|
||
else
|
||
{
|
||
Log.Warning($"尝试减少已经为0的引用计数: {AssetPath}");
|
||
}
|
||
}
|
||
|
||
public bool CanUnload(float expireTime)
|
||
{
|
||
return RefCount <= 0 && expireTime > 0 && (Time.time - LastAccessTime) > expireTime;
|
||
}
|
||
}
|
||
|
||
[Serializable]
|
||
public class PooledObject : IMemory
|
||
{
|
||
public GameObject gameObject;
|
||
public string assetPath;
|
||
public float lastUsedTime;
|
||
public bool isActive;
|
||
public string instanceName;
|
||
public bool isRefCountReduced;
|
||
[NonSerialized] public PoolObjectMonitor monitor;
|
||
|
||
public PooledObject()
|
||
{
|
||
}
|
||
|
||
public PooledObject(GameObject go, string path)
|
||
{
|
||
gameObject = go;
|
||
assetPath = path;
|
||
lastUsedTime = Time.time;
|
||
isActive = false;
|
||
instanceName = go.name;
|
||
isRefCountReduced = false;
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
gameObject = null;
|
||
assetPath = null;
|
||
lastUsedTime = 0f;
|
||
isActive = false;
|
||
instanceName = null;
|
||
isRefCountReduced = false;
|
||
monitor = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取过期进度 (0-1),1表示即将过期。
|
||
/// </summary>
|
||
public float GetExpireProgress(float expireTime)
|
||
{
|
||
if (expireTime <= 0 || isActive) return 0f;
|
||
float timeElapsed = Time.time - lastUsedTime;
|
||
return Mathf.Clamp01(timeElapsed / expireTime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取剩余时间。
|
||
/// </summary>
|
||
public float GetRemainingTime(float expireTime)
|
||
{
|
||
if (expireTime <= 0 || isActive) return -1f;
|
||
float timeElapsed = Time.time - lastUsedTime;
|
||
return Mathf.Max(0f, expireTime - timeElapsed);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inspector显示用的对象信息。
|
||
/// </summary>
|
||
[Serializable]
|
||
public class PoolObjectInfo : IMemory
|
||
{
|
||
[SerializeField] public string objectName;
|
||
[SerializeField] public string assetPath;
|
||
[SerializeField] public bool isActive;
|
||
[SerializeField] public float lastUsedTime;
|
||
[SerializeField] public float remainingTime;
|
||
[SerializeField] public float expireProgress;
|
||
[SerializeField] public GameObject gameObject;
|
||
|
||
public void UpdateFromPooledObject(PooledObject pooledObj, float expireTime)
|
||
{
|
||
objectName = pooledObj.instanceName;
|
||
assetPath = pooledObj.assetPath;
|
||
isActive = pooledObj.isActive;
|
||
lastUsedTime = pooledObj.lastUsedTime;
|
||
remainingTime = pooledObj.GetRemainingTime(expireTime);
|
||
expireProgress = pooledObj.GetExpireProgress(expireTime);
|
||
gameObject = pooledObj.gameObject;
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
objectName = null;
|
||
assetPath = null;
|
||
isActive = false;
|
||
lastUsedTime = 0f;
|
||
remainingTime = 0f;
|
||
expireProgress = 0f;
|
||
gameObject = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inspector显示用的预制体信息.
|
||
/// </summary>
|
||
[Serializable]
|
||
public class PrefabRefInfoDisplay : IMemory
|
||
{
|
||
[SerializeField] public string assetPath;
|
||
[SerializeField] public int refCount;
|
||
[SerializeField] public float lastAccessTime;
|
||
[SerializeField] public GameObject prefab;
|
||
|
||
public void UpdateFromPrefabRefInfo(PrefabRefInfo info)
|
||
{
|
||
assetPath = info.AssetPath;
|
||
refCount = info.RefCount;
|
||
lastAccessTime = info.LastAccessTime;
|
||
prefab = info.Prefab;
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
assetPath = null;
|
||
refCount = 0;
|
||
lastAccessTime = 0f;
|
||
prefab = null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inspector显示用的池信息。
|
||
/// </summary>
|
||
[Serializable]
|
||
public class ConfigPoolInfo : IMemory
|
||
{
|
||
[SerializeField] public string configAsset;
|
||
[SerializeField] public int maxCount;
|
||
[SerializeField] public float expireTime;
|
||
[SerializeField] public int totalObjects;
|
||
[SerializeField] public int activeObjects;
|
||
[SerializeField] public int availableObjects;
|
||
[SerializeField] public int loadedPrefabs;
|
||
[SerializeField] public List<string> assetPaths = new List<string>();
|
||
[SerializeField] public List<PoolObjectInfo> objects = new List<PoolObjectInfo>();
|
||
[SerializeField] public List<PrefabRefInfoDisplay> prefabRefs = new List<PrefabRefInfoDisplay>();
|
||
|
||
public void UpdateFromPool(ConfigPool pool)
|
||
{
|
||
configAsset = pool.Config.asset;
|
||
maxCount = pool.Config.poolCount;
|
||
expireTime = pool.Config.time;
|
||
totalObjects = pool.AllObjects.Count;
|
||
|
||
activeObjects = 0;
|
||
foreach (var obj in pool.AllObjects)
|
||
{
|
||
if (obj.isActive) activeObjects++;
|
||
}
|
||
|
||
// 计算所有队列中的可用对象总数
|
||
availableObjects = 0;
|
||
foreach (var queue in pool.AvailableObjectsByPath.Values)
|
||
{
|
||
availableObjects += queue.Count;
|
||
}
|
||
|
||
loadedPrefabs = pool.LoadedPrefabs.Count;
|
||
|
||
assetPaths.Clear();
|
||
assetPaths.AddRange(pool.LoadedPrefabs.Keys);
|
||
|
||
int objectIndex = 0;
|
||
foreach (var pooledObj in pool.AllObjects)
|
||
{
|
||
if (pooledObj.gameObject != null)
|
||
{
|
||
PoolObjectInfo info;
|
||
if (objectIndex < objects.Count)
|
||
{
|
||
info = objects[objectIndex];
|
||
}
|
||
else
|
||
{
|
||
info = MemoryPool.Acquire<PoolObjectInfo>();
|
||
objects.Add(info);
|
||
}
|
||
|
||
info.UpdateFromPooledObject(pooledObj, pool.Config.time);
|
||
objectIndex++;
|
||
}
|
||
}
|
||
|
||
ReleasePoolObjectInfos(objectIndex);
|
||
|
||
int prefabIndex = 0;
|
||
foreach (var kvp in pool.LoadedPrefabs)
|
||
{
|
||
PrefabRefInfoDisplay info;
|
||
if (prefabIndex < prefabRefs.Count)
|
||
{
|
||
info = prefabRefs[prefabIndex];
|
||
}
|
||
else
|
||
{
|
||
info = MemoryPool.Acquire<PrefabRefInfoDisplay>();
|
||
prefabRefs.Add(info);
|
||
}
|
||
|
||
info.UpdateFromPrefabRefInfo(kvp.Value);
|
||
prefabIndex++;
|
||
}
|
||
|
||
ReleasePrefabRefInfos(prefabIndex);
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
configAsset = null;
|
||
maxCount = 0;
|
||
expireTime = 0f;
|
||
totalObjects = 0;
|
||
activeObjects = 0;
|
||
availableObjects = 0;
|
||
loadedPrefabs = 0;
|
||
assetPaths.Clear();
|
||
ReleasePoolObjectInfos(0);
|
||
ReleasePrefabRefInfos(0);
|
||
}
|
||
|
||
private void ReleasePoolObjectInfos(int keepCount)
|
||
{
|
||
while (objects.Count > keepCount)
|
||
{
|
||
int lastIndex = objects.Count - 1;
|
||
MemoryPool.Release(objects[lastIndex]);
|
||
objects.RemoveAt(lastIndex);
|
||
}
|
||
}
|
||
|
||
private void ReleasePrefabRefInfos(int keepCount)
|
||
{
|
||
while (prefabRefs.Count > keepCount)
|
||
{
|
||
int lastIndex = prefabRefs.Count - 1;
|
||
MemoryPool.Release(prefabRefs[lastIndex]);
|
||
prefabRefs.RemoveAt(lastIndex);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置组对象池 - 管理一个PoolConfig下的所有资源。
|
||
/// </summary>
|
||
public class ConfigPool
|
||
{
|
||
public readonly PoolConfig Config;
|
||
|
||
// 按资源路径分组的可用对象队列
|
||
public readonly Dictionary<string, Queue<PooledObject>> AvailableObjectsByPath;
|
||
public readonly HashSet<PooledObject> AllObjects;
|
||
public readonly Dictionary<string, PrefabRefInfo> LoadedPrefabs;
|
||
public readonly Dictionary<string, List<UniTaskCompletionSource<GameObject>>> PendingRequests;
|
||
public readonly HashSet<string> LoadingAssets;
|
||
public readonly Transform PoolRoot;
|
||
|
||
private readonly IResourceLoader _resourceLoader;
|
||
|
||
// GameObject到PooledObject的快速查找字典
|
||
private readonly Dictionary<GameObject, PooledObject> _gameObjectToPooledObject;
|
||
private readonly List<string> _availableObjectPaths;
|
||
|
||
// 重用临时队列,避免重复创建。
|
||
private static Queue<PooledObject> _tempQueue = new Queue<PooledObject>();
|
||
|
||
// 重用过期对象列表,避免重复创建。
|
||
private static readonly List<PooledObject> _expiredObjects = new List<PooledObject>();
|
||
private static readonly List<string> _expiredPrefabs = new List<string>();
|
||
|
||
public ConfigPool(PoolConfig config, IResourceLoader resourceLoader)
|
||
{
|
||
_resourceLoader = resourceLoader;
|
||
Config = config;
|
||
AvailableObjectsByPath = new Dictionary<string, Queue<PooledObject>>();
|
||
AllObjects = new HashSet<PooledObject>();
|
||
LoadedPrefabs = new Dictionary<string, PrefabRefInfo>();
|
||
PendingRequests = new Dictionary<string, List<UniTaskCompletionSource<GameObject>>>();
|
||
LoadingAssets = new HashSet<string>();
|
||
_gameObjectToPooledObject = new Dictionary<GameObject, PooledObject>();
|
||
_availableObjectPaths = new List<string>();
|
||
|
||
// 创建池根节点。
|
||
GameObject poolRootGo = new GameObject($"ConfigPool_{config.asset.Replace('/', '_')}");
|
||
PoolRoot = poolRootGo.transform;
|
||
PoolRoot.SetParent(GameObjectPool.Instance.poolContainer);
|
||
poolRootGo.SetActive(false);
|
||
}
|
||
|
||
public bool MatchesAsset(string assetPath)
|
||
{
|
||
return assetPath.StartsWith(Config.asset);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步获取对象,如果资源未加载则同步加载。
|
||
/// </summary>
|
||
public GameObject Get(string assetPath)
|
||
{
|
||
if (!LoadedPrefabs.ContainsKey(assetPath))
|
||
{
|
||
if (LoadingAssets.Contains(assetPath))
|
||
{
|
||
Log.Warning($"资源 {assetPath} 正在异步加载中,同步获取可能导致重复加载,建议使用异步方法");
|
||
}
|
||
|
||
try
|
||
{
|
||
GameObject prefab = _resourceLoader.LoadPrefab(assetPath);
|
||
if (prefab != null)
|
||
{
|
||
LoadedPrefabs[assetPath] = new PrefabRefInfo(prefab, assetPath);
|
||
Log.Info($"同步加载资源成功: {assetPath}");
|
||
}
|
||
else
|
||
{
|
||
Log.Error($"同步加载资源失败: {assetPath}");
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Log.Error($"同步加载资源异常: {assetPath}, 错误: {e.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
return GetInternal(assetPath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步获取对象。
|
||
/// </summary>
|
||
public async UniTask<GameObject> GetAsync(string assetPath, CancellationToken cancellationToken = default)
|
||
{
|
||
if (LoadedPrefabs.ContainsKey(assetPath))
|
||
{
|
||
return GetInternal(assetPath);
|
||
}
|
||
|
||
if (LoadingAssets.Contains(assetPath))
|
||
{
|
||
var completionSource = new UniTaskCompletionSource<GameObject>();
|
||
if (!PendingRequests.ContainsKey(assetPath))
|
||
{
|
||
PendingRequests[assetPath] = new List<UniTaskCompletionSource<GameObject>>();
|
||
}
|
||
|
||
PendingRequests[assetPath].Add(completionSource);
|
||
|
||
try
|
||
{
|
||
return await completionSource.Task.AttachExternalCancellation(cancellationToken);
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
PendingRequests[assetPath].Remove(completionSource);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
LoadingAssets.Add(assetPath);
|
||
try
|
||
{
|
||
GameObject prefab = await _resourceLoader.LoadPrefabAsync(assetPath, cancellationToken);
|
||
if (prefab != null)
|
||
{
|
||
LoadedPrefabs[assetPath] = new PrefabRefInfo(prefab, assetPath);
|
||
Log.Info($"异步加载资源成功: {assetPath}");
|
||
|
||
if (PendingRequests.ContainsKey(assetPath))
|
||
{
|
||
var requests = PendingRequests[assetPath];
|
||
PendingRequests.Remove(assetPath);
|
||
|
||
foreach (var request in requests)
|
||
{
|
||
try
|
||
{
|
||
var go = GetInternal(assetPath);
|
||
request.TrySetResult(go);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
request.TrySetException(e);
|
||
}
|
||
}
|
||
}
|
||
|
||
return GetInternal(assetPath);
|
||
}
|
||
else
|
||
{
|
||
throw new Exception($"无法异步加载资源: {assetPath}");
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Log.Error($"异步加载资源失败: {assetPath}, 错误: {e.Message}");
|
||
|
||
if (PendingRequests.ContainsKey(assetPath))
|
||
{
|
||
var requests = PendingRequests[assetPath];
|
||
PendingRequests.Remove(assetPath);
|
||
|
||
foreach (var request in requests)
|
||
{
|
||
request.TrySetException(e);
|
||
}
|
||
}
|
||
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LoadingAssets.Remove(assetPath);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建新的池对象。
|
||
/// </summary>
|
||
private PooledObject CreatePooledObject(string assetPath)
|
||
{
|
||
var prefabRefInfo = LoadedPrefabs[assetPath];
|
||
GameObject instantiate = GameObject.Instantiate(prefabRefInfo.Prefab);
|
||
|
||
var pooledObj = MemoryPool.Acquire<PooledObject>();
|
||
pooledObj.gameObject = instantiate;
|
||
pooledObj.assetPath = assetPath;
|
||
pooledObj.lastUsedTime = Time.time;
|
||
pooledObj.isActive = false;
|
||
pooledObj.instanceName = instantiate.name;
|
||
pooledObj.isRefCountReduced = false;
|
||
|
||
AllObjects.Add(pooledObj);
|
||
_gameObjectToPooledObject[instantiate] = pooledObj;
|
||
|
||
prefabRefInfo.AddRef();
|
||
|
||
var monitor = instantiate.GetComponent<PoolObjectMonitor>();
|
||
if (monitor == null)
|
||
{
|
||
monitor = instantiate.AddComponent<PoolObjectMonitor>();
|
||
}
|
||
|
||
monitor.Initialize(this, pooledObj);
|
||
pooledObj.monitor = monitor;
|
||
return pooledObj;
|
||
}
|
||
|
||
private GameObject GetInternal(string assetPath)
|
||
{
|
||
PooledObject pooledObj = null;
|
||
|
||
// 从按路径分组的队列中获取
|
||
if (AvailableObjectsByPath.TryGetValue(assetPath, out var queue) && queue.Count > 0)
|
||
{
|
||
// 清理已销毁的对象
|
||
while (queue.Count > 0)
|
||
{
|
||
var obj = queue.Dequeue();
|
||
if (obj.gameObject == null)
|
||
{
|
||
// 只有在引用计数未减少时才处理
|
||
if (!obj.isRefCountReduced)
|
||
{
|
||
OnObjectReallyDestroyed(obj);
|
||
}
|
||
else
|
||
{
|
||
// 只需要从集合中移除,不需要减少引用计数
|
||
AllObjects.Remove(obj);
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
pooledObj = obj;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (pooledObj == null)
|
||
{
|
||
if (AllObjects.Count < Config.poolCount)
|
||
{
|
||
pooledObj = CreatePooledObject(assetPath);
|
||
}
|
||
else
|
||
{
|
||
// 使用LINQ找到最旧的未使用对象(更高效)
|
||
PooledObject oldestObj = null;
|
||
foreach (var obj in AllObjects)
|
||
{
|
||
if (obj.isActive)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (oldestObj == null || obj.lastUsedTime < oldestObj.lastUsedTime)
|
||
{
|
||
oldestObj = obj;
|
||
}
|
||
}
|
||
|
||
if (oldestObj != null)
|
||
{
|
||
DestroyPooledObject(oldestObj);
|
||
pooledObj = CreatePooledObject(assetPath);
|
||
}
|
||
else
|
||
{
|
||
Log.Warning($"对象池已满且所有对象都在使用中: {Config.asset},无法创建新对象 {assetPath}");
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
pooledObj.isActive = true;
|
||
pooledObj.lastUsedTime = Time.time;
|
||
pooledObj.gameObject.SetActive(true);
|
||
|
||
return pooledObj.gameObject;
|
||
}
|
||
|
||
public void Return(GameObject go)
|
||
{
|
||
if (!_gameObjectToPooledObject.TryGetValue(go, out var pooledObj))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (pooledObj != null && pooledObj.isActive)
|
||
{
|
||
pooledObj.isActive = false;
|
||
pooledObj.lastUsedTime = Time.time;
|
||
|
||
go.SetActive(false);
|
||
go.transform.SetParent(PoolRoot);
|
||
go.transform.localPosition = Vector3.zero;
|
||
go.transform.localRotation = Quaternion.identity;
|
||
go.transform.localScale = Vector3.one;
|
||
|
||
// 放入按路径分组的队列
|
||
if (!AvailableObjectsByPath.TryGetValue(pooledObj.assetPath, out var queue))
|
||
{
|
||
queue = new Queue<PooledObject>();
|
||
AvailableObjectsByPath[pooledObj.assetPath] = queue;
|
||
}
|
||
|
||
queue.Enqueue(pooledObj);
|
||
}
|
||
}
|
||
|
||
public void OnObjectDestroyed(PooledObject pooledObj)
|
||
{
|
||
// 从GameObjectPool的字典中移除,防止内存泄漏
|
||
if (pooledObj.gameObject != null)
|
||
{
|
||
GameObjectPool.Instance.RemoveGameObjectReference(pooledObj.gameObject);
|
||
}
|
||
|
||
// 防止重复减少引用计数
|
||
if (!pooledObj.isRefCountReduced)
|
||
{
|
||
OnObjectReallyDestroyed(pooledObj);
|
||
MemoryPool.Release(pooledObj);
|
||
}
|
||
else
|
||
{
|
||
// DestroyPooledObject 已调用 Detach,此分支理论上不可达
|
||
AllObjects.Remove(pooledObj);
|
||
CleanAvailableQueue(pooledObj);
|
||
}
|
||
}
|
||
|
||
private void OnObjectReallyDestroyed(PooledObject pooledObj)
|
||
{
|
||
// 标记引用计数已减少,防止重复处理
|
||
if (pooledObj.isRefCountReduced)
|
||
{
|
||
return;
|
||
}
|
||
|
||
pooledObj.isRefCountReduced = true;
|
||
AllObjects.Remove(pooledObj);
|
||
|
||
// 从快速查找字典中移除
|
||
if (pooledObj.gameObject != null)
|
||
{
|
||
_gameObjectToPooledObject.Remove(pooledObj.gameObject);
|
||
}
|
||
|
||
// 减少预制体引用计数
|
||
if (LoadedPrefabs.TryGetValue(pooledObj.assetPath, out PrefabRefInfo refInfo))
|
||
{
|
||
refInfo.RemoveRef();
|
||
}
|
||
|
||
CleanAvailableQueue(pooledObj);
|
||
}
|
||
|
||
// 清理可用队列
|
||
private void CleanAvailableQueue(PooledObject pooledObj)
|
||
{
|
||
// 从对应路径的队列中移除
|
||
if (AvailableObjectsByPath.TryGetValue(pooledObj.assetPath, out var queue))
|
||
{
|
||
_tempQueue.Clear();
|
||
while (queue.Count > 0)
|
||
{
|
||
var obj = queue.Dequeue();
|
||
if (obj != pooledObj)
|
||
{
|
||
_tempQueue.Enqueue(obj);
|
||
}
|
||
}
|
||
|
||
// 交换队列
|
||
(queue, _tempQueue) = (_tempQueue, queue);
|
||
|
||
// 如果队列为空,从字典中移除
|
||
if (queue.Count == 0)
|
||
{
|
||
AvailableObjectsByPath.Remove(pooledObj.assetPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void DestroyPooledObject(PooledObject pooledObj)
|
||
{
|
||
// 先标记引用计数已减少
|
||
if (pooledObj.isRefCountReduced)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 先处理引用计数
|
||
OnObjectReallyDestroyed(pooledObj);
|
||
|
||
if (pooledObj.gameObject != null)
|
||
{
|
||
pooledObj.monitor?.Detach();
|
||
GameObject.Destroy(pooledObj.gameObject);
|
||
}
|
||
|
||
MemoryPool.Release(pooledObj);
|
||
}
|
||
|
||
public void CheckExpiredObjects()
|
||
{
|
||
if (Config.time <= 0) return;
|
||
|
||
float currentTime = Time.time;
|
||
|
||
// 重用过期对象列表
|
||
_expiredObjects.Clear();
|
||
|
||
foreach (var obj in AllObjects)
|
||
{
|
||
if (!obj.isActive && !obj.isRefCountReduced && (currentTime - obj.lastUsedTime) > Config.time)
|
||
{
|
||
_expiredObjects.Add(obj);
|
||
}
|
||
}
|
||
|
||
foreach (var expiredObj in _expiredObjects)
|
||
{
|
||
DestroyPooledObject(expiredObj);
|
||
}
|
||
|
||
// 重建所有路径的可用队列
|
||
_availableObjectPaths.Clear();
|
||
foreach (var assetPath in AvailableObjectsByPath.Keys)
|
||
{
|
||
_availableObjectPaths.Add(assetPath);
|
||
}
|
||
|
||
foreach (var assetPath in _availableObjectPaths)
|
||
{
|
||
if (!AvailableObjectsByPath.TryGetValue(assetPath, out var queue))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
_tempQueue.Clear();
|
||
while (queue.Count > 0)
|
||
{
|
||
var obj = queue.Dequeue();
|
||
if (AllObjects.Contains(obj) && !obj.isRefCountReduced)
|
||
{
|
||
_tempQueue.Enqueue(obj);
|
||
}
|
||
}
|
||
|
||
if (_tempQueue.Count > 0)
|
||
{
|
||
// 交换队列
|
||
(queue, _tempQueue) = (_tempQueue, queue);
|
||
}
|
||
else
|
||
{
|
||
// 队列为空,从字典中移除
|
||
AvailableObjectsByPath.Remove(assetPath);
|
||
}
|
||
}
|
||
|
||
CheckExpiredPrefabs();
|
||
}
|
||
|
||
private void CheckExpiredPrefabs()
|
||
{
|
||
if (Config.time <= 0) return;
|
||
|
||
// 重用过期预制体列表
|
||
_expiredPrefabs.Clear();
|
||
|
||
foreach (var kvp in LoadedPrefabs)
|
||
{
|
||
var refInfo = kvp.Value;
|
||
if (refInfo.CanUnload(Config.time))
|
||
{
|
||
_expiredPrefabs.Add(kvp.Key);
|
||
}
|
||
}
|
||
|
||
foreach (var assetPath in _expiredPrefabs)
|
||
{
|
||
var refInfo = LoadedPrefabs[assetPath];
|
||
Log.Info($"卸载过期预制体: {assetPath}, 引用计数: {refInfo.RefCount}");
|
||
|
||
_resourceLoader.UnloadAsset(refInfo.Prefab);
|
||
LoadedPrefabs.Remove(assetPath);
|
||
}
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
foreach (var obj in AllObjects)
|
||
{
|
||
if (obj.gameObject != null)
|
||
{
|
||
obj.monitor?.Detach();
|
||
GameObject.Destroy(obj.gameObject);
|
||
}
|
||
|
||
MemoryPool.Release(obj);
|
||
}
|
||
|
||
AllObjects.Clear();
|
||
AvailableObjectsByPath.Clear();
|
||
_gameObjectToPooledObject.Clear();
|
||
|
||
foreach (var kvp in LoadedPrefabs)
|
||
{
|
||
var refInfo = kvp.Value;
|
||
if (refInfo.Prefab != null)
|
||
{
|
||
Log.Info($"清理时卸载预制体: {kvp.Key}, 引用计数: {refInfo.RefCount}");
|
||
_resourceLoader.UnloadAsset(refInfo.Prefab);
|
||
}
|
||
}
|
||
|
||
LoadedPrefabs.Clear();
|
||
LoadingAssets.Clear();
|
||
|
||
foreach (var requests in PendingRequests.Values)
|
||
{
|
||
foreach (var request in requests)
|
||
{
|
||
request.TrySetCanceled();
|
||
}
|
||
}
|
||
|
||
PendingRequests.Clear();
|
||
|
||
if (PoolRoot != null)
|
||
{
|
||
GameObject.Destroy(PoolRoot.gameObject);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对象销毁监听器。
|
||
/// </summary>
|
||
public class PoolObjectMonitor : MonoBehaviour
|
||
{
|
||
private ConfigPool _pool;
|
||
private PooledObject _pooledObject;
|
||
|
||
public void Initialize(ConfigPool pool, PooledObject pooledObject)
|
||
{
|
||
_pool = pool;
|
||
_pooledObject = pooledObject;
|
||
}
|
||
|
||
public void Detach()
|
||
{
|
||
_pool = null;
|
||
_pooledObject = null;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (_pool != null && _pooledObject != null)
|
||
{
|
||
_pool.OnObjectDestroyed(_pooledObject);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 游戏对象池管理器。
|
||
/// </summary>
|
||
public class GameObjectPool : MonoBehaviour
|
||
{
|
||
private static GameObjectPool _instance;
|
||
|
||
public static GameObjectPool Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
GameObject go = new GameObject("[GameObjectPool]");
|
||
_instance = go.AddComponent<GameObjectPool>();
|
||
DontDestroyOnLoad(go);
|
||
}
|
||
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
[Header("检查间隔")] public float checkInterval = 10f;
|
||
|
||
[Header("资源加载器")] public bool useEngineResourceLoader = true;
|
||
|
||
[Header("配置路径")] public string poolConfigPath = "Assets/Bundles/Configs/ScriptableObject/PoolConfig";
|
||
|
||
[Header("Inspector显示设置")] public bool showDetailedInfo = true;
|
||
|
||
[Header("池状态信息")] [SerializeField] private List<ConfigPoolInfo> poolInfos = new List<ConfigPoolInfo>();
|
||
|
||
public Transform poolContainer;
|
||
internal IResourceLoader _resourceLoader;
|
||
|
||
private List<PoolConfig> _poolConfigs;
|
||
private List<ConfigPool> _configPools;
|
||
private Dictionary<GameObject, ConfigPool> _gameObjectToPool;
|
||
private Dictionary<string, ConfigPool> _configPoolCache;
|
||
|
||
// 重用预加载对象列表
|
||
private static readonly List<GameObject> _preloadedObjects = new List<GameObject>();
|
||
|
||
private float _lastCleanupTime;
|
||
|
||
private void Awake()
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = this;
|
||
DontDestroyOnLoad(gameObject);
|
||
Initialize();
|
||
}
|
||
else if (_instance != this)
|
||
{
|
||
Destroy(gameObject);
|
||
}
|
||
}
|
||
|
||
private void Initialize()
|
||
{
|
||
_resourceLoader = useEngineResourceLoader ? new AlicizaResourceLoader() as IResourceLoader : new DefaultResourceLoader() as IResourceLoader;
|
||
|
||
GameObject containerGo = new GameObject("PoolContainer");
|
||
poolContainer = containerGo.transform;
|
||
poolContainer.SetParent(transform);
|
||
|
||
_configPools = new List<ConfigPool>();
|
||
_gameObjectToPool = new Dictionary<GameObject, ConfigPool>();
|
||
_configPoolCache = new Dictionary<string, ConfigPool>();
|
||
|
||
try
|
||
{
|
||
_poolConfigs = ModuleSystem.GetModule<IResourceModule>().LoadAsset<PoolConfigScriptableObject>(poolConfigPath).configs;
|
||
_poolConfigs.Sort((a, b) => b.asset.Length.CompareTo(a.asset.Length));
|
||
|
||
foreach (var config in _poolConfigs)
|
||
{
|
||
var configPool = new ConfigPool(config, _resourceLoader);
|
||
_configPools.Add(configPool);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Log.Error($"加载对象池配置失败: {e.Message}");
|
||
_poolConfigs = new List<PoolConfig>();
|
||
}
|
||
|
||
// 初始化清理时间
|
||
_lastCleanupTime = Time.time;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (Time.time - _lastCleanupTime >= checkInterval)
|
||
{
|
||
PerformCleanup();
|
||
_lastCleanupTime = Time.time;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行对象池清理。
|
||
/// </summary>
|
||
private void PerformCleanup()
|
||
{
|
||
if (_configPools == null || _configPools.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
foreach (var pool in _configPools)
|
||
{
|
||
pool.CheckExpiredObjects();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动触发一次清理。
|
||
/// </summary>
|
||
public void ForceCleanup()
|
||
{
|
||
PerformCleanup();
|
||
_lastCleanupTime = Time.time;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部方法:从字典中移除GameObject引用,防止内存泄漏。
|
||
/// </summary>
|
||
internal void RemoveGameObjectReference(GameObject go)
|
||
{
|
||
if (go != null)
|
||
{
|
||
_gameObjectToPool.Remove(go);
|
||
}
|
||
}
|
||
|
||
// Editor专用的刷新。
|
||
private void UpdateInspectorInfo()
|
||
{
|
||
if (_configPools == null)
|
||
{
|
||
ReleaseInspectorInfos(0);
|
||
return;
|
||
}
|
||
|
||
int poolIndex = 0;
|
||
foreach (var pool in _configPools)
|
||
{
|
||
ConfigPoolInfo info;
|
||
if (poolIndex < poolInfos.Count)
|
||
{
|
||
info = poolInfos[poolIndex];
|
||
}
|
||
else
|
||
{
|
||
info = MemoryPool.Acquire<ConfigPoolInfo>();
|
||
poolInfos.Add(info);
|
||
}
|
||
|
||
info.UpdateFromPool(pool);
|
||
poolIndex++;
|
||
}
|
||
|
||
ReleaseInspectorInfos(poolIndex);
|
||
}
|
||
|
||
public void SetResourceLoader(IResourceLoader resourceLoader)
|
||
{
|
||
_resourceLoader = resourceLoader;
|
||
}
|
||
|
||
public GameObject GetGameObject(string assetPath, Transform parent = null)
|
||
{
|
||
ConfigPool pool = FindConfigPool(assetPath);
|
||
GameObject go = null;
|
||
|
||
if (pool != null)
|
||
{
|
||
go = pool.Get(assetPath);
|
||
}
|
||
else
|
||
{
|
||
go = _resourceLoader.LoadGameObject(assetPath, parent);
|
||
}
|
||
|
||
if (go != null && pool != null)
|
||
{
|
||
_gameObjectToPool[go] = pool;
|
||
go.transform.SetParent(parent);
|
||
}
|
||
|
||
return go;
|
||
}
|
||
|
||
public async UniTask<GameObject> GetGameObjectAsync(string assetPath, Transform parent = null, CancellationToken cancellationToken = default)
|
||
{
|
||
ConfigPool pool = FindConfigPool(assetPath);
|
||
GameObject go = null;
|
||
|
||
if (pool != null)
|
||
{
|
||
go = await pool.GetAsync(assetPath, cancellationToken);
|
||
}
|
||
else
|
||
{
|
||
go = await _resourceLoader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
|
||
}
|
||
|
||
if (go != null && pool != null)
|
||
{
|
||
_gameObjectToPool[go] = pool;
|
||
go.transform.SetParent(parent); // 设置父节点
|
||
}
|
||
|
||
return go;
|
||
}
|
||
|
||
public void Release(GameObject go)
|
||
{
|
||
if (go == null) return;
|
||
|
||
if (_gameObjectToPool.TryGetValue(go, out ConfigPool pool))
|
||
{
|
||
pool.Return(go);
|
||
_gameObjectToPool.Remove(go);
|
||
}
|
||
else
|
||
{
|
||
Destroy(go);
|
||
}
|
||
}
|
||
|
||
public async UniTask PreloadAsync(string assetPath, int count = 1, CancellationToken cancellationToken = default)
|
||
{
|
||
ConfigPool pool = FindConfigPool(assetPath);
|
||
if (pool == null)
|
||
{
|
||
Log.Warning($"资源 {assetPath} 没有对应的池配置,无法预加载");
|
||
return;
|
||
}
|
||
|
||
// 优化:重用预加载对象列表
|
||
_preloadedObjects.Clear();
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
GameObject go = await pool.GetAsync(assetPath, cancellationToken);
|
||
if (go != null)
|
||
{
|
||
_preloadedObjects.Add(go);
|
||
}
|
||
}
|
||
|
||
foreach (var go in _preloadedObjects)
|
||
{
|
||
pool.Return(go);
|
||
_gameObjectToPool.Remove(go);
|
||
}
|
||
}
|
||
|
||
public void Preload(string assetPath, int count = 1)
|
||
{
|
||
ConfigPool pool = FindConfigPool(assetPath);
|
||
if (pool == null)
|
||
{
|
||
Log.Warning($"资源 {assetPath} 没有对应的池配置,无法预加载");
|
||
return;
|
||
}
|
||
|
||
// 优化:重用预加载对象列表
|
||
_preloadedObjects.Clear();
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
GameObject go = pool.Get(assetPath);
|
||
if (go != null)
|
||
{
|
||
_preloadedObjects.Add(go);
|
||
}
|
||
}
|
||
|
||
foreach (var go in _preloadedObjects)
|
||
{
|
||
pool.Return(go);
|
||
_gameObjectToPool.Remove(go);
|
||
}
|
||
}
|
||
|
||
private ConfigPool FindConfigPool(string assetPath)
|
||
{
|
||
if (string.IsNullOrEmpty(assetPath))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (_configPools == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (_configPoolCache == null)
|
||
{
|
||
_configPoolCache = new Dictionary<string, ConfigPool>();
|
||
}
|
||
|
||
if (_configPoolCache != null && _configPoolCache.TryGetValue(assetPath, out var cachedPool))
|
||
{
|
||
return cachedPool;
|
||
}
|
||
|
||
foreach (var pool in _configPools)
|
||
{
|
||
if (pool.MatchesAsset(assetPath))
|
||
{
|
||
_configPoolCache[assetPath] = pool;
|
||
return pool;
|
||
}
|
||
}
|
||
|
||
_configPoolCache[assetPath] = null;
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动刷新Inspector信息
|
||
/// </summary>
|
||
public void RefreshInspectorInfo()
|
||
{
|
||
UpdateInspectorInfo();
|
||
}
|
||
|
||
public void ClearAllPools()
|
||
{
|
||
if (_configPools != null)
|
||
{
|
||
foreach (var pool in _configPools)
|
||
{
|
||
pool.Clear();
|
||
}
|
||
}
|
||
|
||
if (_gameObjectToPool != null)
|
||
{
|
||
_gameObjectToPool.Clear();
|
||
}
|
||
|
||
if (_configPoolCache != null)
|
||
{
|
||
_configPoolCache.Clear();
|
||
}
|
||
|
||
ReleaseInspectorInfos(0);
|
||
}
|
||
|
||
private void ReleaseInspectorInfos(int keepCount)
|
||
{
|
||
while (poolInfos.Count > keepCount)
|
||
{
|
||
int lastIndex = poolInfos.Count - 1;
|
||
MemoryPool.Release(poolInfos[lastIndex]);
|
||
poolInfos.RemoveAt(lastIndex);
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
ClearAllPools();
|
||
}
|
||
}
|
||
|
||
public interface IResourceLoader
|
||
{
|
||
GameObject LoadPrefab(string location);
|
||
UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default);
|
||
GameObject LoadGameObject(string location, Transform parent = null);
|
||
UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default);
|
||
void UnloadAsset(GameObject gameObject);
|
||
}
|
||
|
||
public class DefaultResourceLoader : IResourceLoader
|
||
{
|
||
public GameObject LoadPrefab(string location)
|
||
{
|
||
return Resources.Load<GameObject>(location);
|
||
}
|
||
|
||
public async UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
|
||
{
|
||
return await Resources.LoadAsync<GameObject>(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
|
||
}
|
||
|
||
public GameObject LoadGameObject(string location, Transform parent = null)
|
||
{
|
||
var prefab = Resources.Load<GameObject>(location);
|
||
if (prefab == null) return null;
|
||
|
||
var instance = GameObject.Instantiate(prefab);
|
||
if (instance != null && parent != null)
|
||
{
|
||
instance.transform.SetParent(parent);
|
||
}
|
||
|
||
return instance;
|
||
}
|
||
|
||
public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var prefab = await Resources.LoadAsync<GameObject>(location).ToUniTask(cancellationToken: cancellationToken) as GameObject;
|
||
if (prefab == null) return null;
|
||
|
||
var instance = GameObject.Instantiate(prefab);
|
||
if (instance != null && parent != null)
|
||
{
|
||
instance.transform.SetParent(parent);
|
||
}
|
||
|
||
return instance;
|
||
}
|
||
|
||
public void UnloadAsset(GameObject gameObject)
|
||
{
|
||
Resources.UnloadAsset(gameObject);
|
||
}
|
||
}
|
||
|
||
public class AlicizaResourceLoader : IResourceLoader
|
||
{
|
||
private IResourceModule _resourceModule;
|
||
|
||
private void CheckInit()
|
||
{
|
||
if (_resourceModule == null)
|
||
{
|
||
_resourceModule = ModuleSystem.GetModule<IResourceModule>();
|
||
}
|
||
}
|
||
|
||
public GameObject LoadPrefab(string location)
|
||
{
|
||
CheckInit();
|
||
return _resourceModule.LoadAsset<GameObject>(location);
|
||
}
|
||
|
||
public async UniTask<GameObject> LoadPrefabAsync(string location, CancellationToken cancellationToken = default)
|
||
{
|
||
CheckInit();
|
||
return await _resourceModule.LoadAssetAsync<GameObject>(location, cancellationToken);
|
||
}
|
||
|
||
public GameObject LoadGameObject(string location, Transform parent = null)
|
||
{
|
||
CheckInit();
|
||
return _resourceModule.LoadGameObject(location, parent);
|
||
}
|
||
|
||
public async UniTask<GameObject> LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default)
|
||
{
|
||
CheckInit();
|
||
return await _resourceModule.LoadGameObjectAsync(location, parent, cancellationToken);
|
||
}
|
||
|
||
public void UnloadAsset(GameObject gameObject)
|
||
{
|
||
CheckInit();
|
||
_resourceModule.UnloadAsset(gameObject);
|
||
}
|
||
}
|
||
|
||
public static class GameObjectPoolUtil
|
||
{
|
||
public static GameObject LoadGameObject(string assetPath, Transform parent = null)
|
||
{
|
||
return GameObjectPool.Instance.GetGameObject(assetPath, parent);
|
||
}
|
||
|
||
public static async UniTask<GameObject> LoadGameObjectAsync(string assetPath, Transform parent = null, CancellationToken cancellationToken = default)
|
||
{
|
||
return await GameObjectPool.Instance.GetGameObjectAsync(assetPath, parent, cancellationToken);
|
||
}
|
||
|
||
public static void Release(GameObject go)
|
||
{
|
||
GameObjectPool.Instance.Release(go);
|
||
}
|
||
}
|
||
}
|