using System; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; namespace AlicizaX { public readonly struct PoolSpawnContext { public readonly string AssetPath; public readonly string Group; public readonly Transform Parent; public readonly Vector3 Position; public readonly Quaternion Rotation; public readonly object UserData; public readonly int OwnerId; public readonly int TeamId; public readonly uint SpawnFrame; public PoolSpawnContext( string assetPath, string group, Transform parent, Vector3 position, Quaternion rotation, object userData = null, int ownerId = 0, int teamId = 0, uint spawnFrame = 0) { AssetPath = assetPath; Group = group; Parent = parent; Position = position; Rotation = rotation; UserData = userData; OwnerId = ownerId; TeamId = teamId; SpawnFrame = spawnFrame; } public PoolSpawnContext WithParent(Transform parent) { return new PoolSpawnContext(AssetPath, Group, parent, Position, Rotation, UserData, OwnerId, TeamId, SpawnFrame); } public static PoolSpawnContext Create(string assetPath, string group, Transform parent, object userData = null) { return new PoolSpawnContext( assetPath, group, parent, Vector3.zero, Quaternion.identity, userData, spawnFrame: (uint)Time.frameCount); } } public interface IGameObjectPoolable { void OnPoolCreate(); void OnPoolGet(in PoolSpawnContext context); void OnPoolRelease(); void OnPoolDestroy(); } public interface IPoolAutoRecycle { bool TryGetAutoRecycleDelay(out float delaySeconds); } public interface IPoolResettablePhysics { void ResetPhysicsState(); } public interface IPoolResettableVisual { void ResetVisualState(); } public interface IPoolResettableAnimation { void ResetAnimationState(); } public interface IPoolSleepable { void EnterSleep(); void ExitSleep(in PoolSpawnContext context); } public enum RuntimePooledObjectState { Uninitialized = 0, Inactive = 1, Active = 2, Releasing = 3, Destroying = 4 } [Serializable] public sealed class GameObjectPoolInstanceSnapshot : IMemory { public string instanceName; public bool isActive; public float idleDuration; public float lifeDuration; public GameObject gameObject; public void Clear() { instanceName = null; isActive = false; idleDuration = 0f; lifeDuration = 0f; gameObject = null; } } [Serializable] public sealed class GameObjectPoolSnapshot : IMemory { public string entryName; public string group; public string assetPath; public PoolResourceLoaderType loaderType; public PoolOverflowPolicy overflowPolicy; public int minRetained; public int softCapacity; public int hardCapacity; public int totalCount; public int activeCount; public int inactiveCount; public bool prefabLoaded; public float prefabIdleDuration; public int acquireCount; public int releaseCount; public int hitCount; public int missCount; public int expandCount; public int exhaustedCount; public int autoRecycleCount; public int destroyCount; public int peakActive; public int peakTotal; public List instances = new List(); public void Clear() { entryName = null; group = null; assetPath = null; loaderType = default; overflowPolicy = default; minRetained = 0; softCapacity = 0; hardCapacity = 0; totalCount = 0; activeCount = 0; inactiveCount = 0; prefabLoaded = false; prefabIdleDuration = 0f; acquireCount = 0; releaseCount = 0; hitCount = 0; missCount = 0; expandCount = 0; exhaustedCount = 0; autoRecycleCount = 0; destroyCount = 0; peakActive = 0; peakTotal = 0; for (int i = 0; i < instances.Count; i++) { MemoryPool.Release(instances[i]); } instances.Clear(); } } [DisallowMultipleComponent] public sealed class PooledGameObjectMarker : MonoBehaviour { private RuntimePrefabPool _owner; private RuntimePooledInstance _instance; internal void Bind(RuntimePrefabPool owner, RuntimePooledInstance instance) { _owner = owner; _instance = instance; } public void Detach() { _owner = null; _instance = null; } private void OnDestroy() { if (_owner != null && _instance != null) { _owner.NotifyInstanceDestroyed(_instance); } } } [DisallowMultipleComponent] public sealed class PooledAutoRecycle : MonoBehaviour { private GameObjectPoolManager _manager; private CancellationTokenSource _cts; public void Schedule(GameObjectPoolManager manager, float delaySeconds) { Cancel(); if (manager == null || delaySeconds <= 0f) { return; } _manager = manager; _cts = new CancellationTokenSource(); AutoReleaseAsync(delaySeconds, _cts.Token).Forget(); } public void Cancel() { if (_cts == null) { return; } _cts.Cancel(); _cts.Dispose(); _cts = null; } private async UniTaskVoid AutoReleaseAsync(float delaySeconds, CancellationToken cancellationToken) { try { await UniTask.Delay(TimeSpan.FromSeconds(delaySeconds), cancellationToken: cancellationToken); if (_manager != null && this != null && gameObject != null) { _manager.Release(gameObject); } } catch (OperationCanceledException) { } } private void OnDestroy() { Cancel(); _manager = null; } } internal sealed class RuntimePoolLifecycleCache : IMemory { public IGameObjectPoolable[] poolables; public IPoolSleepable[] sleepables; public IPoolResettablePhysics[] physicsResetters; public IPoolResettableVisual[] visualResetters; public IPoolResettableAnimation[] animationResetters; public IPoolAutoRecycle[] autoRecyclers; public void Clear() { poolables = null; sleepables = null; physicsResetters = null; visualResetters = null; animationResetters = null; autoRecyclers = null; } } internal sealed class RuntimePooledInstance : IMemory { public GameObject GameObject { get; private set; } public PooledGameObjectMarker Marker { get; private set; } public PooledAutoRecycle AutoRecycle { get; private set; } public RuntimePoolLifecycleCache Lifecycle { get; private set; } public LinkedListNode InactiveNode { get; set; } public RuntimePooledObjectState State { get; set; } public float LastReleaseTime { get; set; } public float SpawnTime { get; set; } public void Initialize( GameObject gameObject, PooledGameObjectMarker marker, PooledAutoRecycle autoRecycle, RuntimePoolLifecycleCache lifecycle) { GameObject = gameObject; Marker = marker; AutoRecycle = autoRecycle; Lifecycle = lifecycle; State = RuntimePooledObjectState.Inactive; SpawnTime = Time.time; LastReleaseTime = Time.time; } public void Clear() { GameObject = null; Marker = null; AutoRecycle = null; Lifecycle = null; InactiveNode = null; State = RuntimePooledObjectState.Uninitialized; LastReleaseTime = 0f; SpawnTime = 0f; } } internal sealed class RuntimePrefabPool : IMemory { private static readonly Comparison InstanceSnapshotComparer = (left, right) => { if (left == null && right == null) return 0; if (left == null) return 1; if (right == null) return -1; if (left.isActive != right.isActive) return left.isActive ? -1 : 1; return string.Compare(left.instanceName, right.instanceName, StringComparison.Ordinal); }; private ResolvedPoolConfig _config; private string _assetPath; private IResourceLoader _loader; private GameObjectPoolManager _service; private CancellationToken _shutdownToken; private Dictionary _instancesByObject; private LinkedList _inactiveInstances; private List _destroyBuffer; private Transform _root; private GameObject _prefab; private UniTaskCompletionSource _prefabLoadSource; private int _activeCount; private float _lastPrefabTouchTime; private int _runtimeHardCapacity; private int _acquireCount; private int _releaseCount; private int _hitCount; private int _missCount; private int _expandCount; private int _exhaustedCount; private int _autoRecycleCount; private int _destroyCount; private int _peakActive; private int _peakTotal; public RuntimePrefabPool() { _instancesByObject = new Dictionary(); _inactiveInstances = new LinkedList(); _destroyBuffer = new List(); } public void Initialize( ResolvedPoolConfig config, string assetPath, IResourceLoader loader, GameObjectPoolManager service, CancellationToken shutdownToken) { _config = config ?? throw new ArgumentNullException(nameof(config)); _assetPath = assetPath ?? throw new ArgumentNullException(nameof(assetPath)); _loader = loader ?? throw new ArgumentNullException(nameof(loader)); _service = service ?? throw new ArgumentNullException(nameof(service)); _shutdownToken = shutdownToken; _lastPrefabTouchTime = Time.time; _runtimeHardCapacity = config.hardCapacity; _instancesByObject.Clear(); _inactiveInstances.Clear(); _destroyBuffer.Clear(); _prefab = null; _prefabLoadSource = null; _activeCount = 0; _acquireCount = 0; _releaseCount = 0; _hitCount = 0; _missCount = 0; _expandCount = 0; _exhaustedCount = 0; _autoRecycleCount = 0; _destroyCount = 0; _peakActive = 0; _peakTotal = 0; GameObject rootObject = new GameObject($"Pool_{SanitizeName(config.group)}_{SanitizeName(assetPath)}"); _root = rootObject.transform; _root.SetParent(service.poolContainer, false); rootObject.SetActive(false); } public string AssetPath => _assetPath; public ResolvedPoolConfig Config => _config; public int TotalCount => _instancesByObject.Count; public int ActiveCount => _activeCount; public int InactiveCount => _inactiveInstances.Count; public bool IsPrefabLoaded => _prefab != null; public float PrefabIdleDuration => _prefab == null ? 0f : Mathf.Max(0f, Time.time - _lastPrefabTouchTime); public GameObject Acquire(in PoolSpawnContext context) { EnsurePrefabLoaded(); return AcquirePrepared(context); } public async UniTask AcquireAsync(PoolSpawnContext context, CancellationToken cancellationToken) { await EnsurePrefabLoadedAsync(cancellationToken); return AcquirePrepared(context); } public async UniTask WarmupAsync(int targetCount, CancellationToken cancellationToken) { if (targetCount <= 0) { return; } await EnsurePrefabLoadedAsync(cancellationToken); int desiredInactiveCount = Mathf.Clamp(targetCount, 0, _runtimeHardCapacity); if (desiredInactiveCount <= _inactiveInstances.Count) { return; } int desiredTotalCount = Mathf.Min(_runtimeHardCapacity, _activeCount + desiredInactiveCount); if (desiredTotalCount <= _instancesByObject.Count) { return; } float frameBudgetSeconds = _config.warmupFrameBudgetMs * 0.001f; int createdThisFrame = 0; float frameStartTime = Time.realtimeSinceStartup; while (_instancesByObject.Count < desiredTotalCount) { RuntimePooledInstance instance = CreateInstance(); ParkInactive(instance); createdThisFrame++; bool budgetReachedByCount = createdThisFrame >= _config.warmupBatchPerFrame; bool budgetReachedByTime = frameBudgetSeconds > 0f && Time.realtimeSinceStartup - frameStartTime >= frameBudgetSeconds; if (budgetReachedByCount || budgetReachedByTime) { createdThisFrame = 0; frameStartTime = Time.realtimeSinceStartup; await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken); } } } public bool Release(GameObject gameObject) { if (gameObject == null || !_instancesByObject.TryGetValue(gameObject, out RuntimePooledInstance instance)) { return false; } if (instance.State != RuntimePooledObjectState.Active) { return true; } _releaseCount++; InternalReleaseInstance(instance, true); return true; } public void NotifyInstanceDestroyed(RuntimePooledInstance instance) { if (instance == null) { return; } RemoveInstance(instance, false); ReleaseLifecycleCache(instance.Lifecycle); MemoryPool.Release(instance); } public void Trim(int maxDestroyCount, bool aggressive) { if (_config.trimPolicy == PoolTrimPolicy.None) { return; } if (maxDestroyCount <= 0) { maxDestroyCount = _config.trimBatchPerTick; } int destroyBudget = Mathf.Max(1, maxDestroyCount); float now = Time.time; int remainingRetained = Mathf.Max(_config.minRetained, 0); while (destroyBudget > 0 && _inactiveInstances.First != null) { if (_instancesByObject.Count <= remainingRetained) { break; } RuntimePooledInstance candidate = _inactiveInstances.First.Value; bool expired = _config.idleTrimDelay > 0f && now - candidate.LastReleaseTime >= _config.idleTrimDelay; bool overSoftCapacity = _instancesByObject.Count > _config.softCapacity; bool canAggressiveTrim = aggressive && _config.aggressiveTrimOnLowMemory; if (!expired && !overSoftCapacity && !canAggressiveTrim) { break; } DestroyInstance(candidate); destroyBudget--; } if (!CanKeepPrefabLoaded(now, aggressive)) { _loader.UnloadAsset(_prefab); _prefab = null; } } public bool NeedsTrim(float now) { if (_inactiveInstances.First == null) { return false; } if (_instancesByObject.Count > _config.softCapacity) { return true; } if (_config.idleTrimDelay <= 0f) { return false; } return now - _inactiveInstances.First.Value.LastReleaseTime >= _config.idleTrimDelay; } public bool NeedsPrefabUnload(float now, bool aggressive) { return !CanKeepPrefabLoaded(now, aggressive); } public GameObjectPoolSnapshot CreateSnapshot() { var snapshot = MemoryPool.Acquire(); snapshot.entryName = _config.entryName; snapshot.group = _config.group; snapshot.assetPath = _assetPath; snapshot.loaderType = _config.loaderType; snapshot.overflowPolicy = _config.overflowPolicy; snapshot.minRetained = _config.minRetained; snapshot.softCapacity = _config.softCapacity; snapshot.hardCapacity = _runtimeHardCapacity; snapshot.totalCount = _instancesByObject.Count; snapshot.activeCount = _activeCount; snapshot.inactiveCount = _inactiveInstances.Count; snapshot.prefabLoaded = _prefab != null; snapshot.prefabIdleDuration = PrefabIdleDuration; snapshot.acquireCount = _acquireCount; snapshot.releaseCount = _releaseCount; snapshot.hitCount = _hitCount; snapshot.missCount = _missCount; snapshot.expandCount = _expandCount; snapshot.exhaustedCount = _exhaustedCount; snapshot.autoRecycleCount = _autoRecycleCount; snapshot.destroyCount = _destroyCount; snapshot.peakActive = _peakActive; snapshot.peakTotal = _peakTotal; foreach (RuntimePooledInstance instance in _instancesByObject.Values) { var instanceSnapshot = MemoryPool.Acquire(); instanceSnapshot.instanceName = instance.GameObject != null ? instance.GameObject.name : ""; instanceSnapshot.isActive = instance.State == RuntimePooledObjectState.Active; instanceSnapshot.idleDuration = instance.State == RuntimePooledObjectState.Active ? 0f : Mathf.Max(0f, Time.time - instance.LastReleaseTime); instanceSnapshot.lifeDuration = Mathf.Max(0f, Time.time - instance.SpawnTime); instanceSnapshot.gameObject = instance.GameObject; snapshot.instances.Add(instanceSnapshot); } snapshot.instances.Sort(InstanceSnapshotComparer); return snapshot; } public void Shutdown() { _destroyBuffer.AddRange(_instancesByObject.Values); for (int i = 0; i < _destroyBuffer.Count; i++) { DestroyInstance(_destroyBuffer[i]); } _destroyBuffer.Clear(); _inactiveInstances.Clear(); _instancesByObject.Clear(); _activeCount = 0; if (_prefab != null) { _loader.UnloadAsset(_prefab); _prefab = null; } if (_root != null) { GameObject.Destroy(_root.gameObject); } _root = null; } private void EnsurePrefabLoaded() { if (_prefab != null) { TouchPrefab(); return; } if (_prefabLoadSource != null) { throw new InvalidOperationException( $"Pool asset '{_assetPath}' is being loaded asynchronously. Use the async acquire API."); } _prefab = _loader.LoadPrefab(_assetPath); if (_prefab == null) { throw new InvalidOperationException($"Failed to load pooled prefab '{_assetPath}'."); } TouchPrefab(); } private async UniTask EnsurePrefabLoadedAsync(CancellationToken cancellationToken) { if (_prefab != null) { TouchPrefab(); return; } if (_prefabLoadSource != null) { await _prefabLoadSource.Task.AttachExternalCancellation(cancellationToken); return; } _prefabLoadSource = new UniTaskCompletionSource(); try { GameObject prefab = await _loader.LoadPrefabAsync(_assetPath, _shutdownToken); if (prefab == null) { throw new InvalidOperationException($"Failed to load pooled prefab '{_assetPath}'."); } _prefab = prefab; TouchPrefab(); _prefabLoadSource.TrySetResult(prefab); } catch (Exception exception) { _prefabLoadSource.TrySetException(exception); throw; } finally { _prefabLoadSource = null; } } private GameObject AcquirePrepared(in PoolSpawnContext context) { _acquireCount++; RuntimePooledInstance instance = null; if (_inactiveInstances.Last != null) { instance = _inactiveInstances.Last.Value; RemoveInactiveNode(instance); _hitCount++; } else { _missCount++; instance = AcquireOnMiss(); } if (instance == null) { _exhaustedCount++; return null; } ActivateInstance(instance, context); return instance.GameObject; } private RuntimePooledInstance AcquireOnMiss() { if (_instancesByObject.Count < _runtimeHardCapacity) { return CreateInstance(); } switch (_config.overflowPolicy) { case PoolOverflowPolicy.AutoExpand: if (_config.allowRuntimeExpand) { _runtimeHardCapacity++; _expandCount++; return CreateInstance(); } return null; case PoolOverflowPolicy.RecycleOldestInactive: if (_inactiveInstances.First != null) { RuntimePooledInstance oldest = _inactiveInstances.First.Value; RemoveInactiveNode(oldest); return oldest; } return null; case PoolOverflowPolicy.InstantiateOneShot: return CreateInstance(); case PoolOverflowPolicy.DropNewestRequest: case PoolOverflowPolicy.FailFast: default: return null; } } private void ActivateInstance(RuntimePooledInstance instance, in PoolSpawnContext context) { instance.State = RuntimePooledObjectState.Active; _activeCount++; _peakActive = Mathf.Max(_peakActive, _activeCount); _peakTotal = Mathf.Max(_peakTotal, _instancesByObject.Count); TouchPrefab(); GameObject gameObject = instance.GameObject; Transform transform = gameObject.transform; transform.SetParent(context.Parent != null ? context.Parent : null, false); if (context.Position != Vector3.zero || context.Rotation != Quaternion.identity) { transform.SetPositionAndRotation(context.Position, context.Rotation); } ApplyGetLifecycle(instance, context); if (_config.activationMode == PoolActivationMode.SetActive) { gameObject.SetActive(true); } } private RuntimePooledInstance CreateInstance() { GameObject gameObject = GameObject.Instantiate(_prefab); gameObject.name = $"{_prefab.name}(Pool)"; gameObject.SetActive(false); gameObject.transform.SetParent(_root, false); PooledGameObjectMarker marker = gameObject.GetComponent(); if (marker == null) { marker = gameObject.AddComponent(); } PooledAutoRecycle autoRecycle = gameObject.GetComponent(); if (autoRecycle == null) { autoRecycle = gameObject.AddComponent(); } RuntimePoolLifecycleCache lifecycle = BuildLifecycleCache(gameObject); var instance = MemoryPool.Acquire(); instance.Initialize(gameObject, marker, autoRecycle, lifecycle); marker.Bind(this, instance); _instancesByObject.Add(gameObject, instance); _service.RegisterOwnedObject(gameObject, this); InvokeOnPoolCreate(lifecycle); TouchPrefab(); _peakTotal = Mathf.Max(_peakTotal, _instancesByObject.Count); return instance; } private void ParkInactive(RuntimePooledInstance instance) { if (instance == null) { return; } instance.State = RuntimePooledObjectState.Inactive; instance.LastReleaseTime = Time.time; Transform transform = instance.GameObject.transform; transform.SetParent(_root, false); transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; transform.localScale = Vector3.one; instance.GameObject.SetActive(false); instance.InactiveNode = _inactiveInstances.AddLast(instance); _service.NotifyPoolStateChanged(); } private void InternalReleaseInstance(RuntimePooledInstance instance, bool notifyStateChanged) { if (instance == null || instance.GameObject == null) { return; } if (instance.State != RuntimePooledObjectState.Active) { return; } instance.State = RuntimePooledObjectState.Releasing; instance.AutoRecycle?.Cancel(); ApplyReleaseLifecycle(instance); if (_config.activationMode == PoolActivationMode.SetActive) { instance.GameObject.SetActive(false); } _activeCount = Mathf.Max(0, _activeCount - 1); ParkInactive(instance); TouchPrefab(); if (notifyStateChanged) { _service.NotifyPoolStateChanged(); } } private void DestroyInstance(RuntimePooledInstance instance) { if (instance == null) { return; } instance.State = RuntimePooledObjectState.Destroying; RemoveInstance(instance, true); if (instance.GameObject != null) { InvokeOnPoolDestroy(instance.Lifecycle); instance.AutoRecycle?.Cancel(); instance.Marker?.Detach(); GameObject.Destroy(instance.GameObject); } ReleaseLifecycleCache(instance.Lifecycle); MemoryPool.Release(instance); } private void RemoveInstance(RuntimePooledInstance instance, bool countDestroy) { GameObject gameObject = instance?.GameObject; if (gameObject == null) { return; } RemoveInactiveNode(instance); if (instance.State == RuntimePooledObjectState.Active) { _activeCount = Mathf.Max(0, _activeCount - 1); } _instancesByObject.Remove(gameObject); _service.UnregisterOwnedObject(gameObject); if (countDestroy) { _destroyCount++; } TouchPrefab(); _service.NotifyPoolStateChanged(); } private void RemoveInactiveNode(RuntimePooledInstance instance) { if (instance?.InactiveNode == null) { return; } _inactiveInstances.Remove(instance.InactiveNode); instance.InactiveNode = null; } private void ApplyGetLifecycle(RuntimePooledInstance instance, in PoolSpawnContext context) { if (instance.Lifecycle == null) { return; } if (_config.activationMode == PoolActivationMode.SleepWake && instance.Lifecycle.sleepables != null) { for (int i = 0; i < instance.Lifecycle.sleepables.Length; i++) { instance.Lifecycle.sleepables[i]?.ExitSleep(context); } } if (instance.Lifecycle.poolables != null) { for (int i = 0; i < instance.Lifecycle.poolables.Length; i++) { instance.Lifecycle.poolables[i]?.OnPoolGet(context); } } ScheduleAutoRecycle(instance); } private void ApplyReleaseLifecycle(RuntimePooledInstance instance) { if (instance.Lifecycle == null) { return; } if (_config.resetMode == PoolResetMode.FullReset || _config.resetMode == PoolResetMode.PoolableCallbacks) { if (instance.Lifecycle.physicsResetters != null) { for (int i = 0; i < instance.Lifecycle.physicsResetters.Length; i++) { instance.Lifecycle.physicsResetters[i]?.ResetPhysicsState(); } } if (instance.Lifecycle.visualResetters != null) { for (int i = 0; i < instance.Lifecycle.visualResetters.Length; i++) { instance.Lifecycle.visualResetters[i]?.ResetVisualState(); } } if (instance.Lifecycle.animationResetters != null) { for (int i = 0; i < instance.Lifecycle.animationResetters.Length; i++) { instance.Lifecycle.animationResetters[i]?.ResetAnimationState(); } } } if (instance.Lifecycle.poolables != null) { for (int i = 0; i < instance.Lifecycle.poolables.Length; i++) { instance.Lifecycle.poolables[i]?.OnPoolRelease(); } } if (_config.activationMode == PoolActivationMode.SleepWake && instance.Lifecycle.sleepables != null) { for (int i = 0; i < instance.Lifecycle.sleepables.Length; i++) { instance.Lifecycle.sleepables[i]?.EnterSleep(); } } } private void InvokeOnPoolCreate(RuntimePoolLifecycleCache lifecycle) { if (lifecycle?.poolables == null) { return; } for (int i = 0; i < lifecycle.poolables.Length; i++) { lifecycle.poolables[i]?.OnPoolCreate(); } } private void InvokeOnPoolDestroy(RuntimePoolLifecycleCache lifecycle) { if (lifecycle?.poolables == null) { return; } for (int i = 0; i < lifecycle.poolables.Length; i++) { lifecycle.poolables[i]?.OnPoolDestroy(); } } private void ScheduleAutoRecycle(RuntimePooledInstance instance) { if (instance?.AutoRecycle == null) { return; } float delay = _config.autoRecycleDelay; if (delay <= 0f && instance.Lifecycle?.autoRecyclers != null) { for (int i = 0; i < instance.Lifecycle.autoRecyclers.Length; i++) { if (instance.Lifecycle.autoRecyclers[i] != null && instance.Lifecycle.autoRecyclers[i].TryGetAutoRecycleDelay(out delay) && delay > 0f) { break; } } } if (delay > 0f) { _autoRecycleCount++; instance.AutoRecycle.Schedule(_service, delay); } } private RuntimePoolLifecycleCache BuildLifecycleCache(GameObject gameObject) { var cache = MemoryPool.Acquire(); cache.poolables = gameObject.GetComponentsInChildren(true); cache.sleepables = gameObject.GetComponentsInChildren(true); cache.physicsResetters = gameObject.GetComponentsInChildren(true); cache.visualResetters = gameObject.GetComponentsInChildren(true); cache.animationResetters = gameObject.GetComponentsInChildren(true); cache.autoRecyclers = gameObject.GetComponentsInChildren(true); return cache; } private void ReleaseLifecycleCache(RuntimePoolLifecycleCache lifecycle) { if (lifecycle == null) { return; } MemoryPool.Release(lifecycle); } private bool CanKeepPrefabLoaded(float now, bool aggressive) { if (_prefab == null) { return true; } if (_config.keepPrefabResident) { return true; } if (_instancesByObject.Count > 0) { return true; } if (_config.prefabUnloadDelay <= 0f) { return false; } if (aggressive && _config.aggressiveTrimOnLowMemory) { return false; } return now - _lastPrefabTouchTime < _config.prefabUnloadDelay; } private void TouchPrefab() { _lastPrefabTouchTime = Time.time; } public void Clear() { _config = null; _assetPath = null; _loader = null; _service = null; _shutdownToken = default; _instancesByObject.Clear(); _inactiveInstances.Clear(); _destroyBuffer.Clear(); _root = null; _prefab = null; _prefabLoadSource = null; _activeCount = 0; _lastPrefabTouchTime = 0f; _runtimeHardCapacity = 0; _acquireCount = 0; _releaseCount = 0; _hitCount = 0; _missCount = 0; _expandCount = 0; _exhaustedCount = 0; _autoRecycleCount = 0; _destroyCount = 0; _peakActive = 0; _peakTotal = 0; } private static string SanitizeName(string value) { return string.IsNullOrWhiteSpace(value) ? "Unnamed" : value.Replace('/', '_').Replace('\\', '_').Replace(':', '_'); } } }