com.alicizax.unity.framework/Runtime/ABase/GameObjectPool/GameObjectPoolManager.cs

709 lines
23 KiB
C#
Raw Normal View History

2026-03-26 10:49:41 +08:00
using System;
using System.Collections.Generic;
using System.Threading;
2026-04-30 17:18:17 +08:00
using AlicizaX.ObjectPool;
2026-03-26 10:49:41 +08:00
using AlicizaX.Resource.Runtime;
2026-04-30 17:18:17 +08:00
using Cysharp.Text;
2026-03-26 10:49:41 +08:00
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace AlicizaX
{
2026-03-26 19:50:59 +08:00
public sealed class GameObjectPoolManager : MonoServiceBehaviour<AppScope>
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
private enum BootstrapState : byte
2026-03-26 13:30:57 +08:00
{
2026-04-30 17:18:17 +08:00
Waiting = 0,
Ready = 1
}
private struct MaintenanceNode
{
public float dueTime;
public int poolIndex;
}
private static readonly Comparison<GameObjectPoolSnapshot> SnapshotComparer = CompareSnapshot;
private const PoolResourceLoaderType DefaultDirectLoader = PoolResourceLoaderType.AssetBundle;
[Header("Config Path")]
public string poolConfigPath = "Assets/Bundles/Configs/PoolConfig";
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
[Header("Show Detailed Info")]
public bool showDetailedInfo = true;
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
[SerializeField]
internal Transform poolContainer;
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
private readonly IResourceLoader[] _resourceLoaders = new IResourceLoader[2];
private readonly List<GameObjectPoolSnapshot> _debugSnapshots = new List<GameObjectPoolSnapshot>(16);
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
private RuntimeGameObjectPool[] _pools = new RuntimeGameObjectPool[8];
private int _poolCount;
private PoolCompiledCatalog _catalog = PoolCompiledCatalog.Empty();
private StringOpenHashMap[] _rulePoolMaps = Array.Empty<StringOpenHashMap>();
private bool[] _rulePoolMapInitialized = Array.Empty<bool>();
private StringOpenHashMap _directLoadWarnedPaths = new StringOpenHashMap(8);
private StringOpenHashMap _groupRootMap = new StringOpenHashMap(8);
private Transform[] _groupRoots = new Transform[4];
private int _groupRootCount;
private BootstrapState _bootstrapState;
2026-03-26 10:49:41 +08:00
private CancellationTokenSource _shutdownTokenSource;
2026-03-26 13:30:57 +08:00
2026-04-30 17:18:17 +08:00
private MaintenanceNode[] _maintenanceHeap = new MaintenanceNode[8];
private int _maintenanceCount;
public bool IsReady => _bootstrapState == BootstrapState.Ready;
internal CancellationToken ShutdownToken => _shutdownTokenSource == null ? default : _shutdownTokenSource.Token;
2026-03-26 10:49:41 +08:00
2026-03-26 19:55:46 +08:00
protected override void OnInitialize()
2026-03-26 10:49:41 +08:00
{
_shutdownTokenSource = new CancellationTokenSource();
EnsureDefaultResourceLoaders();
EnsurePoolContainer();
Application.lowMemory += OnLowMemory;
2026-04-30 17:18:17 +08:00
_bootstrapState = BootstrapState.Waiting;
enabled = true;
2026-03-26 10:49:41 +08:00
}
2026-03-26 19:55:46 +08:00
protected override void OnDestroyService()
2026-03-26 10:49:41 +08:00
{
Application.lowMemory -= OnLowMemory;
2026-03-26 10:49:41 +08:00
_shutdownTokenSource?.Cancel();
ClearAllPools();
if (poolContainer != null)
{
Destroy(poolContainer.gameObject);
poolContainer = null;
}
_shutdownTokenSource?.Dispose();
_shutdownTokenSource = null;
}
private void Update()
{
2026-04-30 17:18:17 +08:00
if (_bootstrapState == BootstrapState.Waiting)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
TryBootstrap();
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
if (_bootstrapState != BootstrapState.Ready)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
enabled = true;
2026-03-26 10:49:41 +08:00
return;
}
2026-04-30 17:18:17 +08:00
float now = Time.time;
ProcessDueMaintenance(now);
enabled = _bootstrapState == BootstrapState.Waiting || _maintenanceCount > 0;
2026-03-26 10:49:41 +08:00
}
public void SetResourceLoader(IResourceLoader resourceLoader)
{
SetResourceLoader(PoolResourceLoaderType.AssetBundle, resourceLoader);
}
public void SetResourceLoader(PoolResourceLoaderType loaderType, IResourceLoader resourceLoader)
{
if (resourceLoader == null)
{
throw new ArgumentNullException(nameof(resourceLoader));
}
2026-04-30 17:18:17 +08:00
_resourceLoaders[(int)loaderType] = resourceLoader;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
public GameObject GetGameObject(string assetPath, Transform parent = null)
2026-04-17 21:01:20 +08:00
{
EnsureReadyForSyncUse();
2026-04-30 17:18:17 +08:00
string normalizedAssetPath = PoolEntry.NormalizeAssetPath(assetPath);
PoolSpawnContext context = PoolSpawnContext.Create(normalizedAssetPath, parent);
return GetGameObjectInternal(normalizedAssetPath, context);
2026-03-26 10:49:41 +08:00
}
public async UniTask<GameObject> GetGameObjectAsync(
string assetPath,
Transform parent = null,
2026-04-17 21:01:20 +08:00
CancellationToken cancellationToken = default)
{
2026-04-30 17:18:17 +08:00
await EnsureReadyAsync(cancellationToken);
string normalizedAssetPath = PoolEntry.NormalizeAssetPath(assetPath);
PoolSpawnContext context = PoolSpawnContext.Create(normalizedAssetPath, parent);
return await GetGameObjectInternalAsync(normalizedAssetPath, context, cancellationToken);
2026-03-26 10:49:41 +08:00
}
public void Release(GameObject gameObject)
{
if (gameObject == null)
{
return;
}
2026-04-30 17:18:17 +08:00
if (gameObject.TryGetComponent(out GameObjectPoolHandle handle) && handle.TryRelease())
2026-03-26 10:49:41 +08:00
{
return;
}
Destroy(gameObject);
}
public async UniTask PreloadAsync(string assetPath, int count = 1, CancellationToken cancellationToken = default)
{
2026-04-30 17:18:17 +08:00
await EnsureReadyAsync(cancellationToken);
await PreloadInternalAsync(PoolEntry.NormalizeAssetPath(assetPath), count, cancellationToken);
2026-03-26 10:49:41 +08:00
}
public void ForceCleanup()
{
2026-04-30 17:18:17 +08:00
if (_bootstrapState != BootstrapState.Ready)
2026-03-26 10:49:41 +08:00
{
return;
}
2026-04-30 17:18:17 +08:00
float now = Time.time;
for (int i = 0; i < _poolCount; i++)
{
RuntimeGameObjectPool pool = _pools[i];
if (pool != null)
{
pool.ExecuteMaintenance(now, false);
}
}
2026-03-26 10:49:41 +08:00
}
public void ClearAllPools()
{
2026-04-30 17:18:17 +08:00
for (int i = 0; i < _poolCount; i++)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
RuntimeGameObjectPool pool = _pools[i];
if (pool == null)
{
continue;
}
2026-03-26 10:49:41 +08:00
pool.Shutdown();
MemoryPool.Release(pool);
2026-04-30 17:18:17 +08:00
_pools[i] = null;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
_poolCount = 0;
_maintenanceCount = 0;
for (int i = 0; i < _rulePoolMapInitialized.Length; i++)
{
if (_rulePoolMapInitialized[i])
{
_rulePoolMaps[i].Clear();
}
}
ClearGroupRoots();
_directLoadWarnedPaths.Clear();
2026-03-26 10:49:41 +08:00
ReleaseDebugSnapshots();
2026-04-30 17:18:17 +08:00
}
public GameObjectPoolSummarySnapshot GetDebugSummary()
{
int loadedPrefabCount = 0;
int totalInstanceCount = 0;
int activeInstanceCount = 0;
int inactiveInstanceCount = 0;
for (int i = 0; i < _poolCount; i++)
{
RuntimeGameObjectPool pool = _pools[i];
if (pool == null)
{
continue;
}
if (pool.IsPrefabLoaded)
{
loadedPrefabCount++;
}
totalInstanceCount += pool.TotalCount;
activeInstanceCount += pool.ActiveCount;
inactiveInstanceCount += pool.InactiveCount;
}
return new GameObjectPoolSummarySnapshot(
_bootstrapState == BootstrapState.Ready,
_bootstrapState == BootstrapState.Waiting,
_poolCount,
loadedPrefabCount,
totalInstanceCount,
activeInstanceCount,
inactiveInstanceCount,
_maintenanceCount);
2026-03-26 10:49:41 +08:00
}
public List<GameObjectPoolSnapshot> GetDebugSnapshots()
{
ReleaseDebugSnapshots();
2026-04-30 17:18:17 +08:00
for (int i = 0; i < _poolCount; i++)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
RuntimeGameObjectPool pool = _pools[i];
if (pool != null)
{
_debugSnapshots.Add(pool.CreateSnapshot());
}
2026-03-26 10:49:41 +08:00
}
2026-03-26 13:30:57 +08:00
_debugSnapshots.Sort(SnapshotComparer);
2026-03-26 10:49:41 +08:00
return _debugSnapshots;
}
2026-04-30 17:18:17 +08:00
internal void ScheduleMaintenance(int poolIndex, float dueTime, ref int heapIndex)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
if (dueTime >= float.MaxValue)
{
RemoveMaintenance(ref heapIndex);
return;
}
if (heapIndex >= 0)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
_maintenanceHeap[heapIndex].dueTime = dueTime;
_maintenanceHeap[heapIndex].poolIndex = poolIndex;
SiftMaintenanceUp(heapIndex);
SiftMaintenanceDown(heapIndex);
enabled = true;
2026-03-26 10:49:41 +08:00
return;
}
2026-04-30 17:18:17 +08:00
EnsureMaintenanceCapacity(_maintenanceCount + 1);
int insertIndex = _maintenanceCount++;
_maintenanceHeap[insertIndex].dueTime = dueTime;
_maintenanceHeap[insertIndex].poolIndex = poolIndex;
heapIndex = insertIndex;
_pools[poolIndex].SetMaintenanceHeapIndex(insertIndex);
SiftMaintenanceUp(insertIndex);
enabled = true;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
internal void RemoveMaintenance(ref int heapIndex)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
if (heapIndex < 0 || heapIndex >= _maintenanceCount)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
heapIndex = -1;
2026-03-26 10:49:41 +08:00
return;
}
2026-04-30 17:18:17 +08:00
RemoveMaintenanceAt(heapIndex);
heapIndex = -1;
}
2026-04-30 17:18:17 +08:00
private bool TryBootstrap()
{
2026-04-30 17:18:17 +08:00
if (_bootstrapState == BootstrapState.Ready)
{
2026-04-30 17:18:17 +08:00
return true;
}
2026-04-30 17:18:17 +08:00
if (!YooAsset.YooAssets.Initialized)
{
return false;
}
LoadCatalog();
_bootstrapState = BootstrapState.Ready;
enabled = _maintenanceCount > 0;
return true;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void LoadCatalog()
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
IResourceService resourceService = AppServices.Require<IResourceService>();
PoolConfigScriptableObject configAsset = resourceService.LoadAsset<PoolConfigScriptableObject>(poolConfigPath);
_catalog = configAsset == null ? PoolCompiledCatalog.Empty() : configAsset.BuildCatalog();
if (configAsset != null)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
resourceService.UnloadAsset(configAsset);
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
_rulePoolMaps = _catalog.RuleCount == 0 ? Array.Empty<StringOpenHashMap>() : new StringOpenHashMap[_catalog.RuleCount];
_rulePoolMapInitialized = _catalog.RuleCount == 0 ? Array.Empty<bool>() : new bool[_catalog.RuleCount];
}
private GameObject GetGameObjectInternal(string assetPath, in PoolSpawnContext context)
{
int ruleIndex = _catalog.Resolve(assetPath, null);
if (ruleIndex < 0)
{
WarnDirectLoadFallback(assetPath);
return LoadDirect(assetPath, context.Parent);
}
ref readonly PoolCompiledRule rule = ref _catalog.GetRule(ruleIndex);
RuntimeGameObjectPool pool = GetOrCreatePool(ruleIndex, assetPath);
return pool.Acquire(context.WithGroup(rule.group));
2026-03-26 10:49:41 +08:00
}
private async UniTask<GameObject> GetGameObjectInternalAsync(
string assetPath,
2026-04-17 21:01:20 +08:00
PoolSpawnContext context,
2026-03-26 10:49:41 +08:00
CancellationToken cancellationToken)
{
2026-04-30 17:18:17 +08:00
int ruleIndex = _catalog.Resolve(assetPath, null);
if (ruleIndex < 0)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
WarnDirectLoadFallback(assetPath);
return await LoadDirectAsync(assetPath, context.Parent, cancellationToken);
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
string ruleGroup = _catalog.GetRule(ruleIndex).group;
RuntimeGameObjectPool pool = GetOrCreatePool(ruleIndex, assetPath);
return await pool.AcquireAsync(context.WithGroup(ruleGroup), cancellationToken);
2026-03-26 10:49:41 +08:00
}
private async UniTask PreloadInternalAsync(
string assetPath,
int count,
CancellationToken cancellationToken)
{
if (count <= 0)
{
return;
}
2026-04-30 17:18:17 +08:00
int ruleIndex = _catalog.Resolve(assetPath, null);
if (ruleIndex < 0)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
WarnDirectLoadFallback(assetPath);
2026-03-26 10:49:41 +08:00
return;
}
2026-04-30 17:18:17 +08:00
RuntimeGameObjectPool pool = GetOrCreatePool(ruleIndex, assetPath);
2026-03-26 10:49:41 +08:00
await pool.WarmupAsync(count, cancellationToken);
}
2026-04-30 17:18:17 +08:00
private RuntimeGameObjectPool GetOrCreatePool(int ruleIndex, string assetPath)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
if (!_rulePoolMapInitialized[ruleIndex])
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
_rulePoolMaps[ruleIndex] = new StringOpenHashMap(4);
_rulePoolMapInitialized[ruleIndex] = true;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
if (_rulePoolMaps[ruleIndex].TryGetValue(assetPath, out int poolIndex))
{
return _pools[poolIndex];
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
EnsurePoolCapacity(_poolCount + 1);
ref readonly PoolCompiledRule rule = ref _catalog.GetRule(ruleIndex);
var pool = MemoryPool.Acquire<RuntimeGameObjectPool>();
pool.Initialize(this, _poolCount, rule, assetPath, GetResourceLoader(rule.loaderType), GetOrCreateGroupRoot(rule.group));
_pools[_poolCount] = pool;
_rulePoolMaps[ruleIndex].AddOrUpdate(assetPath, _poolCount);
_poolCount++;
enabled = true;
2026-03-26 10:49:41 +08:00
return pool;
}
2026-04-30 17:18:17 +08:00
private GameObject LoadDirect(string assetPath, Transform parent)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
IResourceLoader loader = GetResourceLoader(DefaultDirectLoader);
return loader.LoadGameObject(assetPath, parent);
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private async UniTask<GameObject> LoadDirectAsync(string assetPath, Transform parent, CancellationToken cancellationToken)
{
2026-04-30 17:18:17 +08:00
IResourceLoader loader = GetResourceLoader(DefaultDirectLoader);
return await loader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
}
2026-04-30 17:18:17 +08:00
private IResourceLoader GetResourceLoader(PoolResourceLoaderType loaderType)
{
2026-04-30 17:18:17 +08:00
IResourceLoader loader = _resourceLoaders[(int)loaderType];
if (loader == null)
{
2026-04-30 17:18:17 +08:00
throw new InvalidOperationException(ZString.Format("Resource loader not registered: {0}.", loaderType));
}
2026-04-30 17:18:17 +08:00
return loader;
}
2026-03-26 10:49:41 +08:00
private void EnsureDefaultResourceLoaders()
{
2026-04-30 17:18:17 +08:00
if (_resourceLoaders[(int)PoolResourceLoaderType.AssetBundle] == null)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
_resourceLoaders[(int)PoolResourceLoaderType.AssetBundle] = new AssetBundleResourceLoader();
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
if (_resourceLoaders[(int)PoolResourceLoaderType.Resources] == null)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
_resourceLoaders[(int)PoolResourceLoaderType.Resources] = new UnityResourcesLoader();
2026-03-26 10:49:41 +08:00
}
}
private void EnsurePoolContainer()
{
if (poolContainer != null)
{
return;
}
2026-04-30 17:18:17 +08:00
GameObject container = new GameObject("GameObjectPoolContainer");
2026-03-26 10:49:41 +08:00
poolContainer = container.transform;
poolContainer.SetParent(transform, false);
}
2026-04-30 17:18:17 +08:00
private Transform GetOrCreateGroupRoot(string group)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
string groupName = string.IsNullOrWhiteSpace(group) ? PoolEntry.DefaultGroup : group.Trim();
if (_groupRootMap.TryGetValue(groupName, out int groupIndex))
{
Transform existingRoot = _groupRoots[groupIndex];
if (existingRoot != null)
{
return existingRoot;
}
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
EnsureGroupRootCapacity(_groupRootCount + 1);
GameObject rootObject = new GameObject(ZString.Format("[{0}]", groupName));
Transform root = rootObject.transform;
root.SetParent(poolContainer, false);
rootObject.SetActive(true);
int newIndex = _groupRootCount++;
_groupRoots[newIndex] = root;
_groupRootMap.AddOrUpdate(groupName, newIndex);
return root;
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
private void EnsureGroupRootCapacity(int required)
{
if (_groupRoots.Length >= required)
2026-03-26 10:49:41 +08:00
{
return;
}
2026-04-30 17:18:17 +08:00
int newCapacity = Mathf.Max(required, _groupRoots.Length << 1);
var newRoots = new Transform[newCapacity];
Array.Copy(_groupRoots, 0, newRoots, 0, _groupRootCount);
_groupRoots = newRoots;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void ClearGroupRoots()
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
for (int i = 0; i < _groupRootCount; i++)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
Transform root = _groupRoots[i];
if (root != null)
{
Destroy(root.gameObject);
_groupRoots[i] = null;
}
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
_groupRootCount = 0;
_groupRootMap.Clear();
}
private void WarnDirectLoadFallback(string assetPath)
{
if (_directLoadWarnedPaths.TryGetValue(assetPath, out _))
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
return;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
_directLoadWarnedPaths.AddOrUpdate(assetPath, 1);
Log.Warning(ZString.Format(
"[GameObjectPool] Asset not found in PoolConfig. Fallback to direct load and Release() will destroy it. Asset:{0}",
assetPath));
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
private void EnsureReadyForSyncUse()
{
if (!TryBootstrap())
{
throw new InvalidOperationException(
"GameObjectPool is waiting for resource bootstrap. Use async APIs or call after YooAssets initialization.");
2026-03-26 10:49:41 +08:00
}
}
2026-04-30 17:18:17 +08:00
private async UniTask EnsureReadyAsync(CancellationToken cancellationToken)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
while (!TryBootstrap())
{
cancellationToken.ThrowIfCancellationRequested();
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken);
}
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void OnLowMemory()
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
if (_bootstrapState != BootstrapState.Ready)
{
return;
}
float now = Time.time;
for (int i = 0; i < _poolCount; i++)
{
RuntimeGameObjectPool pool = _pools[i];
if (pool != null)
{
pool.ExecuteMaintenance(now, true);
}
}
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void ProcessDueMaintenance(float now)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
while (_maintenanceCount > 0)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
MaintenanceNode node = _maintenanceHeap[0];
if (node.dueTime > now)
2026-04-17 21:01:20 +08:00
{
2026-04-30 17:18:17 +08:00
return;
2026-04-17 21:01:20 +08:00
}
2026-04-30 17:18:17 +08:00
RemoveMaintenanceAt(0);
RuntimeGameObjectPool pool = _pools[node.poolIndex];
pool?.ExecuteMaintenance(now, false);
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
private void EnsurePoolCapacity(int required)
{
if (_pools.Length >= required)
{
return;
}
int newCapacity = Mathf.Max(required, _pools.Length << 1);
var newPools = new RuntimeGameObjectPool[newCapacity];
Array.Copy(_pools, 0, newPools, 0, _poolCount);
_pools = newPools;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void EnsureMaintenanceCapacity(int required)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
if (_maintenanceHeap.Length >= required)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
return;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
int newCapacity = Mathf.Max(required, _maintenanceHeap.Length << 1);
var newHeap = new MaintenanceNode[newCapacity];
Array.Copy(_maintenanceHeap, 0, newHeap, 0, _maintenanceCount);
_maintenanceHeap = newHeap;
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
private void RemoveMaintenanceAt(int heapIndex)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
MaintenanceNode removed = _maintenanceHeap[heapIndex];
RuntimeGameObjectPool removedPool = _pools[removed.poolIndex];
removedPool?.SetMaintenanceHeapIndex(-1);
int lastIndex = _maintenanceCount - 1;
if (heapIndex != lastIndex)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
MaintenanceNode moved = _maintenanceHeap[lastIndex];
_maintenanceHeap[heapIndex] = moved;
RuntimeGameObjectPool movedPool = _pools[moved.poolIndex];
movedPool?.SetMaintenanceHeapIndex(heapIndex);
2026-03-26 10:49:41 +08:00
}
2026-04-30 17:18:17 +08:00
_maintenanceHeap[lastIndex] = default;
_maintenanceCount = lastIndex;
if (heapIndex < _maintenanceCount)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
SiftMaintenanceUp(heapIndex);
SiftMaintenanceDown(heapIndex);
2026-03-26 10:49:41 +08:00
}
}
2026-04-30 17:18:17 +08:00
private void SiftMaintenanceUp(int index)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
while (index > 0)
{
int parent = (index - 1) >> 1;
if (_maintenanceHeap[parent].dueTime <= _maintenanceHeap[index].dueTime)
{
break;
}
2026-03-26 10:49:41 +08:00
2026-04-30 17:18:17 +08:00
SwapMaintenance(parent, index);
index = parent;
}
}
private void SiftMaintenanceDown(int index)
{
while (true)
2026-03-26 10:49:41 +08:00
{
2026-04-30 17:18:17 +08:00
int left = (index << 1) + 1;
if (left >= _maintenanceCount)
{
return;
}
int right = left + 1;
int smallest = right < _maintenanceCount && _maintenanceHeap[right].dueTime < _maintenanceHeap[left].dueTime
? right
: left;
if (_maintenanceHeap[index].dueTime <= _maintenanceHeap[smallest].dueTime)
{
return;
}
SwapMaintenance(index, smallest);
index = smallest;
2026-03-26 10:49:41 +08:00
}
}
2026-04-30 17:18:17 +08:00
private void SwapMaintenance(int left, int right)
{
MaintenanceNode temp = _maintenanceHeap[left];
_maintenanceHeap[left] = _maintenanceHeap[right];
_maintenanceHeap[right] = temp;
_pools[_maintenanceHeap[left].poolIndex]?.SetMaintenanceHeapIndex(left);
_pools[_maintenanceHeap[right].poolIndex]?.SetMaintenanceHeapIndex(right);
}
2026-03-26 10:49:41 +08:00
private void ReleaseDebugSnapshots()
{
for (int i = 0; i < _debugSnapshots.Count; i++)
{
MemoryPool.Release(_debugSnapshots[i]);
}
_debugSnapshots.Clear();
}
2026-04-17 21:01:20 +08:00
2026-04-30 17:18:17 +08:00
private static int CompareSnapshot(GameObjectPoolSnapshot left, GameObjectPoolSnapshot right)
2026-04-17 21:01:20 +08:00
{
2026-04-30 17:18:17 +08:00
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return 1;
}
if (right == null)
{
return -1;
}
int groupCompare = string.Compare(left.group, right.group, StringComparison.Ordinal);
if (groupCompare != 0)
{
return groupCompare;
}
return string.Compare(left.assetPath, right.assetPath, StringComparison.Ordinal);
2026-04-17 21:01:20 +08:00
}
2026-03-26 10:49:41 +08:00
}
}