607 lines
20 KiB
C#
607 lines
20 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Threading;
|
||
|
|
using AlicizaX.Resource.Runtime;
|
||
|
|
using Cysharp.Threading.Tasks;
|
||
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
namespace AlicizaX
|
||
|
|
{
|
||
|
|
public sealed class GameObjectPool : MonoServiceBehaviour<GameObjectPool>
|
||
|
|
{
|
||
|
|
[Header("检查间隔")]
|
||
|
|
public float checkInterval = 10f;
|
||
|
|
|
||
|
|
[Header("配置路径")]
|
||
|
|
public string poolConfigPath = "Assets/Bundles/Configs/PoolConfig";
|
||
|
|
|
||
|
|
[Header("Inspector显示设置")]
|
||
|
|
public bool showDetailedInfo = true;
|
||
|
|
|
||
|
|
[SerializeField]
|
||
|
|
internal Transform poolContainer;
|
||
|
|
|
||
|
|
private const PoolResourceLoaderType DefaultDirectLoadResourceLoaderType = PoolResourceLoaderType.AssetBundle;
|
||
|
|
|
||
|
|
private readonly Dictionary<string, RuntimePrefabPool> _poolsByKey = new Dictionary<string, RuntimePrefabPool>(StringComparer.Ordinal);
|
||
|
|
private readonly Dictionary<string, PoolConfig> _resolvedConfigCache = new Dictionary<string, PoolConfig>(StringComparer.Ordinal);
|
||
|
|
private readonly Dictionary<GameObject, RuntimePrefabPool> _ownersByObject = new Dictionary<GameObject, RuntimePrefabPool>();
|
||
|
|
private readonly Dictionary<PoolResourceLoaderType, IResourceLoader> _resourceLoaders = new Dictionary<PoolResourceLoaderType, IResourceLoader>();
|
||
|
|
private readonly List<PoolConfig> _configs = new List<PoolConfig>();
|
||
|
|
private readonly List<GameObjectPoolSnapshot> _debugSnapshots = new List<GameObjectPoolSnapshot>();
|
||
|
|
|
||
|
|
private CancellationTokenSource _shutdownTokenSource;
|
||
|
|
private UniTask _initializeTask;
|
||
|
|
private bool _initializationCompleted;
|
||
|
|
private Exception _initializationException;
|
||
|
|
private float _lastCleanupTime;
|
||
|
|
|
||
|
|
public bool IsReady => _initializationCompleted && _initializationException == null;
|
||
|
|
|
||
|
|
protected override void OnServiceInitialize()
|
||
|
|
{
|
||
|
|
_shutdownTokenSource = new CancellationTokenSource();
|
||
|
|
EnsureDefaultResourceLoaders();
|
||
|
|
EnsurePoolContainer();
|
||
|
|
_initializeTask = InitializeAsync(_shutdownTokenSource.Token);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void OnServiceDestroy()
|
||
|
|
{
|
||
|
|
_shutdownTokenSource?.Cancel();
|
||
|
|
ClearAllPools();
|
||
|
|
|
||
|
|
if (poolContainer != null)
|
||
|
|
{
|
||
|
|
Destroy(poolContainer.gameObject);
|
||
|
|
poolContainer = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
_shutdownTokenSource?.Dispose();
|
||
|
|
_shutdownTokenSource = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask InitializeAsync(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await UniTask.WaitUntil(() => YooAsset.YooAssets.Initialized, cancellationToken: cancellationToken);
|
||
|
|
LoadConfigs();
|
||
|
|
_lastCleanupTime = Time.time;
|
||
|
|
_initializationCompleted = true;
|
||
|
|
await PrewarmConfiguredPoolsAsync(cancellationToken);
|
||
|
|
}
|
||
|
|
catch (OperationCanceledException)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
catch (Exception exception)
|
||
|
|
{
|
||
|
|
_initializationException = exception;
|
||
|
|
_initializationCompleted = true;
|
||
|
|
Log.Error($"GameObjectPool initialization failed: {exception}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void Update()
|
||
|
|
{
|
||
|
|
if (!IsReady)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Time.time - _lastCleanupTime < checkInterval)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
PerformCleanup();
|
||
|
|
_lastCleanupTime = Time.time;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SetResourceLoader(IResourceLoader resourceLoader)
|
||
|
|
{
|
||
|
|
SetResourceLoader(PoolResourceLoaderType.AssetBundle, resourceLoader);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SetResourceLoader(PoolResourceLoaderType loaderType, IResourceLoader resourceLoader)
|
||
|
|
{
|
||
|
|
if (resourceLoader == null)
|
||
|
|
{
|
||
|
|
throw new ArgumentNullException(nameof(resourceLoader));
|
||
|
|
}
|
||
|
|
|
||
|
|
_resourceLoaders[loaderType] = resourceLoader;
|
||
|
|
}
|
||
|
|
|
||
|
|
public GameObject GetGameObject(string assetPath, Transform parent = null)
|
||
|
|
{
|
||
|
|
EnsureReadyForSyncUse();
|
||
|
|
return GetGameObjectInternal(PoolConfig.NormalizeAssetPath(assetPath), null, parent);
|
||
|
|
}
|
||
|
|
|
||
|
|
public GameObject GetGameObjectByGroup(string group, string assetPath, Transform parent = null)
|
||
|
|
{
|
||
|
|
EnsureReadyForSyncUse();
|
||
|
|
return GetGameObjectInternal(PoolConfig.NormalizeAssetPath(assetPath), group, parent);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async UniTask<GameObject> GetGameObjectAsync(
|
||
|
|
string assetPath,
|
||
|
|
Transform parent = null,
|
||
|
|
CancellationToken cancellationToken = default)
|
||
|
|
{
|
||
|
|
await EnsureInitializedAsync(cancellationToken);
|
||
|
|
return await GetGameObjectInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), null, parent, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async UniTask<GameObject> GetGameObjectAsyncByGroup(
|
||
|
|
string group,
|
||
|
|
string assetPath,
|
||
|
|
Transform parent = null,
|
||
|
|
CancellationToken cancellationToken = default)
|
||
|
|
{
|
||
|
|
await EnsureInitializedAsync(cancellationToken);
|
||
|
|
return await GetGameObjectInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), group, parent, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Release(GameObject gameObject)
|
||
|
|
{
|
||
|
|
if (gameObject == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_ownersByObject.TryGetValue(gameObject, out RuntimePrefabPool pool))
|
||
|
|
{
|
||
|
|
pool.Release(gameObject);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Log.Warning($"Trying to release untracked GameObject '{gameObject.name}'. Destroying it.");
|
||
|
|
Destroy(gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Preload(string assetPath, int count = 1)
|
||
|
|
{
|
||
|
|
EnsureReadyForSyncUse();
|
||
|
|
PreloadInternal(PoolConfig.NormalizeAssetPath(assetPath), null, count);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void PreloadByGroup(string group, string assetPath, int count = 1)
|
||
|
|
{
|
||
|
|
EnsureReadyForSyncUse();
|
||
|
|
PreloadInternal(PoolConfig.NormalizeAssetPath(assetPath), group, count);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async UniTask PreloadAsync(string assetPath, int count = 1, CancellationToken cancellationToken = default)
|
||
|
|
{
|
||
|
|
await EnsureInitializedAsync(cancellationToken);
|
||
|
|
await PreloadInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), null, count, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async UniTask PreloadAsyncByGroup(
|
||
|
|
string group,
|
||
|
|
string assetPath,
|
||
|
|
int count = 1,
|
||
|
|
CancellationToken cancellationToken = default)
|
||
|
|
{
|
||
|
|
await EnsureInitializedAsync(cancellationToken);
|
||
|
|
await PreloadInternalAsync(PoolConfig.NormalizeAssetPath(assetPath), group, count, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ForceCleanup()
|
||
|
|
{
|
||
|
|
if (!IsReady)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
PerformCleanup();
|
||
|
|
_lastCleanupTime = Time.time;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ClearAllPools()
|
||
|
|
{
|
||
|
|
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
||
|
|
{
|
||
|
|
pool.Shutdown();
|
||
|
|
MemoryPool.Release(pool);
|
||
|
|
}
|
||
|
|
|
||
|
|
_poolsByKey.Clear();
|
||
|
|
_ownersByObject.Clear();
|
||
|
|
_resolvedConfigCache.Clear();
|
||
|
|
ReleaseDebugSnapshots();
|
||
|
|
}
|
||
|
|
|
||
|
|
public List<GameObjectPoolSnapshot> GetDebugSnapshots()
|
||
|
|
{
|
||
|
|
ReleaseDebugSnapshots();
|
||
|
|
|
||
|
|
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
||
|
|
{
|
||
|
|
_debugSnapshots.Add(pool.CreateSnapshot());
|
||
|
|
}
|
||
|
|
|
||
|
|
_debugSnapshots.Sort((left, right) =>
|
||
|
|
{
|
||
|
|
if (left == null && right == null)
|
||
|
|
{
|
||
|
|
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);
|
||
|
|
});
|
||
|
|
|
||
|
|
return _debugSnapshots;
|
||
|
|
}
|
||
|
|
|
||
|
|
internal void RegisterOwnedObject(GameObject gameObject, RuntimePrefabPool pool)
|
||
|
|
{
|
||
|
|
if (gameObject == null || pool == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_ownersByObject[gameObject] = pool;
|
||
|
|
}
|
||
|
|
|
||
|
|
internal void UnregisterOwnedObject(GameObject gameObject)
|
||
|
|
{
|
||
|
|
if (gameObject == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_ownersByObject.Remove(gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
private GameObject GetGameObjectInternal(string assetPath, string group, Transform parent)
|
||
|
|
{
|
||
|
|
PoolConfig config = ResolveConfig(assetPath, group);
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
return LoadUnpooled(assetPath, group, parent);
|
||
|
|
}
|
||
|
|
|
||
|
|
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
||
|
|
return pool.Acquire(parent);
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask<GameObject> GetGameObjectInternalAsync(
|
||
|
|
string assetPath,
|
||
|
|
string group,
|
||
|
|
Transform parent,
|
||
|
|
CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
PoolConfig config = ResolveConfig(assetPath, group);
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
return await LoadUnpooledAsync(assetPath, group, parent, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
||
|
|
return await pool.AcquireAsync(parent, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void PreloadInternal(string assetPath, string group, int count)
|
||
|
|
{
|
||
|
|
if (count <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
PoolConfig config = ResolveConfig(assetPath, group);
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
Log.Warning($"Asset '{assetPath}' has no matching pool config. Preload skipped.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
||
|
|
pool.Warmup(count);
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask PreloadInternalAsync(
|
||
|
|
string assetPath,
|
||
|
|
string group,
|
||
|
|
int count,
|
||
|
|
CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
if (count <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
PoolConfig config = ResolveConfig(assetPath, group);
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
Log.Warning($"Asset '{assetPath}' has no matching pool config. Preload skipped.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
RuntimePrefabPool pool = GetOrCreatePool(config, assetPath);
|
||
|
|
await pool.WarmupAsync(count, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
private RuntimePrefabPool GetOrCreatePool(PoolConfig config, string assetPath)
|
||
|
|
{
|
||
|
|
EnsurePoolContainer();
|
||
|
|
|
||
|
|
string poolKey = config.BuildResolvedPoolKey(assetPath);
|
||
|
|
if (_poolsByKey.TryGetValue(poolKey, out RuntimePrefabPool existingPool))
|
||
|
|
{
|
||
|
|
return existingPool;
|
||
|
|
}
|
||
|
|
|
||
|
|
var pool = MemoryPool.Acquire<RuntimePrefabPool>();
|
||
|
|
pool.Initialize(
|
||
|
|
config,
|
||
|
|
assetPath,
|
||
|
|
GetResourceLoader(config.resourceLoaderType),
|
||
|
|
this,
|
||
|
|
_shutdownTokenSource != null ? _shutdownTokenSource.Token : default);
|
||
|
|
|
||
|
|
_poolsByKey.Add(poolKey, pool);
|
||
|
|
return pool;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void PerformCleanup()
|
||
|
|
{
|
||
|
|
float now = Time.time;
|
||
|
|
foreach (RuntimePrefabPool pool in _poolsByKey.Values)
|
||
|
|
{
|
||
|
|
pool.TrimExpiredInstances(now);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void EnsureDefaultResourceLoaders()
|
||
|
|
{
|
||
|
|
if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.AssetBundle))
|
||
|
|
{
|
||
|
|
_resourceLoaders[PoolResourceLoaderType.AssetBundle] = new AlicizaResourceLoader();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.Resources))
|
||
|
|
{
|
||
|
|
_resourceLoaders[PoolResourceLoaderType.Resources] = new UnityResourcesLoader();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void EnsurePoolContainer()
|
||
|
|
{
|
||
|
|
if (poolContainer != null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var container = new GameObject("GameObjectPoolContainer");
|
||
|
|
poolContainer = container.transform;
|
||
|
|
poolContainer.SetParent(transform, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void LoadConfigs()
|
||
|
|
{
|
||
|
|
_configs.Clear();
|
||
|
|
_resolvedConfigCache.Clear();
|
||
|
|
|
||
|
|
PoolConfigScriptableObject configAsset =
|
||
|
|
ModuleSystem.GetModule<IResourceModule>().LoadAsset<PoolConfigScriptableObject>(poolConfigPath);
|
||
|
|
|
||
|
|
if (configAsset == null || configAsset.configs == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int i = 0; i < configAsset.configs.Count; i++)
|
||
|
|
{
|
||
|
|
PoolConfig config = configAsset.configs[i];
|
||
|
|
if (config == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
config.Normalize();
|
||
|
|
if (string.IsNullOrWhiteSpace(config.assetPath))
|
||
|
|
{
|
||
|
|
Log.Warning($"PoolConfig at index {i} has an empty asset path and was ignored.");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
_configs.Add(config);
|
||
|
|
}
|
||
|
|
|
||
|
|
_configs.Sort(PoolConfig.CompareByPriority);
|
||
|
|
LogConfigWarnings();
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask PrewarmConfiguredPoolsAsync(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < _configs.Count; i++)
|
||
|
|
{
|
||
|
|
PoolConfig config = _configs[i];
|
||
|
|
if (!config.preloadOnInitialize || config.prewarmCount <= 0)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (config.matchMode != PoolMatchMode.Exact)
|
||
|
|
{
|
||
|
|
Log.Warning(
|
||
|
|
$"PoolConfig '{config.assetPath}' uses Prefix mode and preloadOnInitialize. Prefix rules cannot infer a concrete asset to prewarm.");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
RuntimePrefabPool pool = GetOrCreatePool(config, config.assetPath);
|
||
|
|
await pool.WarmupAsync(config.prewarmCount, cancellationToken);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private PoolConfig ResolveConfig(string assetPath, string group)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(assetPath))
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
string cacheKey = $"{group ?? string.Empty}|{assetPath}";
|
||
|
|
if (_resolvedConfigCache.TryGetValue(cacheKey, out PoolConfig cachedConfig))
|
||
|
|
{
|
||
|
|
return cachedConfig;
|
||
|
|
}
|
||
|
|
|
||
|
|
PoolConfig matchedConfig = null;
|
||
|
|
for (int i = 0; i < _configs.Count; i++)
|
||
|
|
{
|
||
|
|
PoolConfig candidate = _configs[i];
|
||
|
|
if (!candidate.Matches(assetPath, group))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (matchedConfig == null)
|
||
|
|
{
|
||
|
|
matchedConfig = candidate;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool samePriority =
|
||
|
|
matchedConfig.matchMode == candidate.matchMode &&
|
||
|
|
matchedConfig.assetPath.Length == candidate.assetPath.Length;
|
||
|
|
|
||
|
|
if (samePriority)
|
||
|
|
{
|
||
|
|
Log.Warning(
|
||
|
|
$"Asset '{assetPath}' matched multiple pool configs with the same priority. Using '{matchedConfig.group}:{matchedConfig.assetPath}'.");
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
_resolvedConfigCache[cacheKey] = matchedConfig;
|
||
|
|
return matchedConfig;
|
||
|
|
}
|
||
|
|
|
||
|
|
private GameObject LoadUnpooled(string assetPath, string group, Transform parent)
|
||
|
|
{
|
||
|
|
IResourceLoader loader = GetDirectLoadResourceLoader(group);
|
||
|
|
return loader.LoadGameObject(assetPath, parent);
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask<GameObject> LoadUnpooledAsync(
|
||
|
|
string assetPath,
|
||
|
|
string group,
|
||
|
|
Transform parent,
|
||
|
|
CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
IResourceLoader loader = GetDirectLoadResourceLoader(group);
|
||
|
|
return await loader.LoadGameObjectAsync(assetPath, parent, cancellationToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
private IResourceLoader GetDirectLoadResourceLoader(string group)
|
||
|
|
{
|
||
|
|
if (!string.IsNullOrWhiteSpace(group))
|
||
|
|
{
|
||
|
|
for (int i = 0; i < _configs.Count; i++)
|
||
|
|
{
|
||
|
|
PoolConfig config = _configs[i];
|
||
|
|
if (string.Equals(config.group, group, StringComparison.Ordinal))
|
||
|
|
{
|
||
|
|
return GetResourceLoader(config.resourceLoaderType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return GetResourceLoader(DefaultDirectLoadResourceLoaderType);
|
||
|
|
}
|
||
|
|
|
||
|
|
private IResourceLoader GetResourceLoader(PoolResourceLoaderType loaderType)
|
||
|
|
{
|
||
|
|
if (_resourceLoaders.TryGetValue(loaderType, out IResourceLoader loader) && loader != null)
|
||
|
|
{
|
||
|
|
return loader;
|
||
|
|
}
|
||
|
|
|
||
|
|
throw new InvalidOperationException($"Resource loader not registered: {loaderType}");
|
||
|
|
}
|
||
|
|
|
||
|
|
private void EnsureReadyForSyncUse()
|
||
|
|
{
|
||
|
|
if (!_initializationCompleted)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
"GameObjectPool is still initializing. Use the async APIs or wait until initialization completes.");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_initializationException != null)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException("GameObjectPool initialization failed.", _initializationException);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async UniTask EnsureInitializedAsync(CancellationToken cancellationToken)
|
||
|
|
{
|
||
|
|
await _initializeTask.AttachExternalCancellation(cancellationToken);
|
||
|
|
|
||
|
|
if (_initializationException != null)
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException("GameObjectPool initialization failed.", _initializationException);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void LogConfigWarnings()
|
||
|
|
{
|
||
|
|
for (int i = 0; i < _configs.Count; i++)
|
||
|
|
{
|
||
|
|
PoolConfig left = _configs[i];
|
||
|
|
for (int j = i + 1; j < _configs.Count; j++)
|
||
|
|
{
|
||
|
|
PoolConfig right = _configs[j];
|
||
|
|
if (!string.Equals(left.group, right.group, StringComparison.Ordinal))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (left.matchMode != right.matchMode)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!string.Equals(left.assetPath, right.assetPath, StringComparison.Ordinal))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Log.Warning($"Duplicate pool config detected: '{left.group}:{left.assetPath}'.");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ReleaseDebugSnapshots()
|
||
|
|
{
|
||
|
|
for (int i = 0; i < _debugSnapshots.Count; i++)
|
||
|
|
{
|
||
|
|
MemoryPool.Release(_debugSnapshots[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
_debugSnapshots.Clear();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|