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();
|
2026-03-31 17:25:20 +08:00
|
|
|
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
|
|
|
{
|
2026-03-31 17:25:20 +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-03-26 16:14:05 +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-03-31 17:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-30 17:18:17 +08:00
|
|
|
private bool TryBootstrap()
|
2026-03-31 17:25:20 +08:00
|
|
|
{
|
2026-04-30 17:18:17 +08:00
|
|
|
if (_bootstrapState == BootstrapState.Ready)
|
2026-03-31 17:25:20 +08:00
|
|
|
{
|
2026-04-30 17:18:17 +08:00
|
|
|
return true;
|
2026-03-31 17:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
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-03-31 17:25:20 +08:00
|
|
|
{
|
2026-04-30 17:18:17 +08:00
|
|
|
IResourceLoader loader = GetResourceLoader(DefaultDirectLoader);
|
|
|
|
|
return await loader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
|
2026-03-31 17:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-30 17:18:17 +08:00
|
|
|
private IResourceLoader GetResourceLoader(PoolResourceLoaderType loaderType)
|
2026-03-31 17:25:20 +08:00
|
|
|
{
|
2026-04-30 17:18:17 +08:00
|
|
|
IResourceLoader loader = _resourceLoaders[(int)loaderType];
|
|
|
|
|
if (loader == null)
|
2026-03-31 17:25:20 +08:00
|
|
|
{
|
2026-04-30 17:18:17 +08:00
|
|
|
throw new InvalidOperationException(ZString.Format("Resource loader not registered: {0}.", loaderType));
|
2026-03-31 17:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-30 17:18:17 +08:00
|
|
|
return loader;
|
2026-03-31 17:25:20 +08:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|