2026-03-26 10:49:41 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using Cysharp.Threading.Tasks;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace AlicizaX
|
|
|
|
|
{
|
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class GameObjectPoolInstanceSnapshot : IMemory
|
|
|
|
|
{
|
|
|
|
|
public string instanceName;
|
|
|
|
|
public bool isActive;
|
|
|
|
|
public float idleDuration;
|
|
|
|
|
public GameObject gameObject;
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
instanceName = null;
|
|
|
|
|
isActive = false;
|
|
|
|
|
idleDuration = 0f;
|
|
|
|
|
gameObject = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
public sealed class GameObjectPoolSnapshot : IMemory
|
|
|
|
|
{
|
|
|
|
|
public string group;
|
|
|
|
|
public string assetPath;
|
|
|
|
|
public PoolMatchMode matchMode;
|
|
|
|
|
public PoolResourceLoaderType loaderType;
|
|
|
|
|
public int capacity;
|
|
|
|
|
public int totalCount;
|
|
|
|
|
public int activeCount;
|
|
|
|
|
public int inactiveCount;
|
|
|
|
|
public float instanceIdleTimeout;
|
|
|
|
|
public float prefabUnloadDelay;
|
|
|
|
|
public bool prefabLoaded;
|
|
|
|
|
public float prefabIdleDuration;
|
|
|
|
|
public List<GameObjectPoolInstanceSnapshot> instances = new List<GameObjectPoolInstanceSnapshot>();
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
group = null;
|
|
|
|
|
assetPath = null;
|
|
|
|
|
matchMode = default;
|
|
|
|
|
loaderType = default;
|
|
|
|
|
capacity = 0;
|
|
|
|
|
totalCount = 0;
|
|
|
|
|
activeCount = 0;
|
|
|
|
|
inactiveCount = 0;
|
|
|
|
|
instanceIdleTimeout = 0f;
|
|
|
|
|
prefabUnloadDelay = 0f;
|
|
|
|
|
prefabLoaded = false;
|
|
|
|
|
prefabIdleDuration = 0f;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal sealed class RuntimePooledInstance : IMemory
|
|
|
|
|
{
|
|
|
|
|
public GameObject GameObject { get; private set; }
|
|
|
|
|
public PooledGameObjectMarker Marker { get; private set; }
|
|
|
|
|
public LinkedListNode<RuntimePooledInstance> InactiveNode { get; set; }
|
|
|
|
|
public bool IsActive { get; set; }
|
|
|
|
|
public float LastReleaseTime { get; set; }
|
|
|
|
|
|
|
|
|
|
public void Initialize(GameObject gameObject, PooledGameObjectMarker marker)
|
|
|
|
|
{
|
|
|
|
|
GameObject = gameObject;
|
|
|
|
|
Marker = marker;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
GameObject = null;
|
|
|
|
|
Marker = null;
|
|
|
|
|
InactiveNode = null;
|
|
|
|
|
IsActive = false;
|
|
|
|
|
LastReleaseTime = 0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal sealed class RuntimePrefabPool : IMemory
|
|
|
|
|
{
|
2026-03-26 13:30:57 +08:00
|
|
|
private static readonly Comparison<GameObjectPoolInstanceSnapshot> 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);
|
|
|
|
|
};
|
2026-03-26 10:49:41 +08:00
|
|
|
private PoolConfig _config;
|
|
|
|
|
private string _assetPath;
|
|
|
|
|
private IResourceLoader _loader;
|
2026-03-26 16:14:05 +08:00
|
|
|
private GameObjectPoolManager _service;
|
2026-03-26 10:49:41 +08:00
|
|
|
private CancellationToken _shutdownToken;
|
|
|
|
|
private Dictionary<GameObject, RuntimePooledInstance> _instancesByObject;
|
|
|
|
|
private LinkedList<RuntimePooledInstance> _inactiveInstances;
|
2026-03-26 13:30:57 +08:00
|
|
|
private List<RuntimePooledInstance> _shutdownBuffer;
|
2026-03-26 10:49:41 +08:00
|
|
|
private Transform _root;
|
|
|
|
|
|
|
|
|
|
private GameObject _prefab;
|
|
|
|
|
private UniTaskCompletionSource<GameObject> _prefabLoadSource;
|
|
|
|
|
private int _activeCount;
|
|
|
|
|
private float _lastPrefabTouchTime;
|
|
|
|
|
|
|
|
|
|
public RuntimePrefabPool()
|
|
|
|
|
{
|
|
|
|
|
_instancesByObject = new Dictionary<GameObject, RuntimePooledInstance>();
|
|
|
|
|
_inactiveInstances = new LinkedList<RuntimePooledInstance>();
|
2026-03-26 13:30:57 +08:00
|
|
|
_shutdownBuffer = new List<RuntimePooledInstance>();
|
2026-03-26 10:49:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Initialize(
|
|
|
|
|
PoolConfig config,
|
|
|
|
|
string assetPath,
|
|
|
|
|
IResourceLoader loader,
|
2026-03-26 16:14:05 +08:00
|
|
|
GameObjectPoolManager service,
|
2026-03-26 10:49:41 +08:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
_instancesByObject.Clear();
|
|
|
|
|
_inactiveInstances.Clear();
|
|
|
|
|
_prefab = null;
|
|
|
|
|
_prefabLoadSource = null;
|
|
|
|
|
_activeCount = 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 PoolConfig 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(Transform parent)
|
|
|
|
|
{
|
|
|
|
|
EnsurePrefabLoaded();
|
|
|
|
|
return AcquirePrepared(parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async UniTask<GameObject> AcquireAsync(Transform parent, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
await EnsurePrefabLoadedAsync(cancellationToken);
|
|
|
|
|
return AcquirePrepared(parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Warmup(int count)
|
|
|
|
|
{
|
|
|
|
|
if (count <= 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EnsurePrefabLoaded();
|
|
|
|
|
WarmupPrepared(count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async UniTask WarmupAsync(int count, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
if (count <= 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await EnsurePrefabLoadedAsync(cancellationToken);
|
|
|
|
|
WarmupPrepared(count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Release(GameObject gameObject)
|
|
|
|
|
{
|
|
|
|
|
if (gameObject == null || !_instancesByObject.TryGetValue(gameObject, out RuntimePooledInstance instance))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!instance.IsActive)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance.IsActive = false;
|
|
|
|
|
instance.LastReleaseTime = Time.time;
|
|
|
|
|
_activeCount = Mathf.Max(0, _activeCount - 1);
|
|
|
|
|
|
2026-03-26 13:30:57 +08:00
|
|
|
gameObject.SetActive(false);
|
|
|
|
|
|
2026-03-26 10:49:41 +08:00
|
|
|
Transform transform = gameObject.transform;
|
|
|
|
|
transform.SetParent(_root, false);
|
|
|
|
|
transform.localPosition = Vector3.zero;
|
|
|
|
|
transform.localRotation = Quaternion.identity;
|
|
|
|
|
transform.localScale = Vector3.one;
|
|
|
|
|
|
|
|
|
|
instance.InactiveNode = _inactiveInstances.AddLast(instance);
|
|
|
|
|
TouchPrefab();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void NotifyInstanceDestroyed(RuntimePooledInstance instance)
|
|
|
|
|
{
|
|
|
|
|
if (instance == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveInstance(instance);
|
|
|
|
|
MemoryPool.Release(instance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void TrimExpiredInstances(float now)
|
|
|
|
|
{
|
|
|
|
|
if (_config.instanceIdleTimeout > 0f)
|
|
|
|
|
{
|
|
|
|
|
while (_inactiveInstances.First != null)
|
|
|
|
|
{
|
|
|
|
|
RuntimePooledInstance candidate = _inactiveInstances.First.Value;
|
|
|
|
|
if (now - candidate.LastReleaseTime < _config.instanceIdleTimeout)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyInstance(candidate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_prefab != null &&
|
|
|
|
|
_instancesByObject.Count == 0 &&
|
|
|
|
|
_config.prefabUnloadDelay > 0f &&
|
|
|
|
|
now - _lastPrefabTouchTime >= _config.prefabUnloadDelay)
|
|
|
|
|
{
|
|
|
|
|
_loader.UnloadAsset(_prefab);
|
|
|
|
|
_prefab = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public GameObjectPoolSnapshot CreateSnapshot()
|
|
|
|
|
{
|
|
|
|
|
var snapshot = MemoryPool.Acquire<GameObjectPoolSnapshot>();
|
|
|
|
|
snapshot.group = _config.group;
|
|
|
|
|
snapshot.assetPath = _assetPath;
|
|
|
|
|
snapshot.matchMode = _config.matchMode;
|
|
|
|
|
snapshot.loaderType = _config.resourceLoaderType;
|
|
|
|
|
snapshot.capacity = _config.capacity;
|
|
|
|
|
snapshot.totalCount = _instancesByObject.Count;
|
|
|
|
|
snapshot.activeCount = _activeCount;
|
|
|
|
|
snapshot.inactiveCount = _inactiveInstances.Count;
|
|
|
|
|
snapshot.instanceIdleTimeout = _config.instanceIdleTimeout;
|
|
|
|
|
snapshot.prefabUnloadDelay = _config.prefabUnloadDelay;
|
|
|
|
|
snapshot.prefabLoaded = _prefab != null;
|
|
|
|
|
snapshot.prefabIdleDuration = PrefabIdleDuration;
|
|
|
|
|
|
|
|
|
|
foreach (RuntimePooledInstance instance in _instancesByObject.Values)
|
|
|
|
|
{
|
|
|
|
|
var instanceSnapshot = MemoryPool.Acquire<GameObjectPoolInstanceSnapshot>();
|
|
|
|
|
instanceSnapshot.instanceName = instance.GameObject != null ? instance.GameObject.name : "<destroyed>";
|
|
|
|
|
instanceSnapshot.isActive = instance.IsActive;
|
|
|
|
|
instanceSnapshot.idleDuration = instance.IsActive ? 0f : Mathf.Max(0f, Time.time - instance.LastReleaseTime);
|
|
|
|
|
instanceSnapshot.gameObject = instance.GameObject;
|
|
|
|
|
snapshot.instances.Add(instanceSnapshot);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 13:30:57 +08:00
|
|
|
snapshot.instances.Sort(InstanceSnapshotComparer);
|
2026-03-26 10:49:41 +08:00
|
|
|
|
|
|
|
|
return snapshot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Shutdown()
|
|
|
|
|
{
|
2026-03-26 13:30:57 +08:00
|
|
|
_shutdownBuffer.AddRange(_instancesByObject.Values);
|
|
|
|
|
foreach (RuntimePooledInstance instance in _shutdownBuffer)
|
2026-03-26 10:49:41 +08:00
|
|
|
{
|
|
|
|
|
DestroyInstance(instance);
|
|
|
|
|
}
|
2026-03-26 13:30:57 +08:00
|
|
|
_shutdownBuffer.Clear();
|
2026-03-26 10:49:41 +08:00
|
|
|
|
|
|
|
|
_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<GameObject>();
|
|
|
|
|
|
|
|
|
|
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 void WarmupPrepared(int requestedCount)
|
|
|
|
|
{
|
|
|
|
|
int targetCount = Mathf.Clamp(requestedCount, 0, _config.capacity);
|
|
|
|
|
if (targetCount <= _instancesByObject.Count)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int toCreate = targetCount - _instancesByObject.Count;
|
|
|
|
|
for (int i = 0; i < toCreate; i++)
|
|
|
|
|
{
|
|
|
|
|
RuntimePooledInstance instance = CreateInstance();
|
|
|
|
|
ParkInactive(instance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private GameObject AcquirePrepared(Transform parent)
|
|
|
|
|
{
|
|
|
|
|
RuntimePooledInstance instance = null;
|
|
|
|
|
|
|
|
|
|
if (_inactiveInstances.Last != null)
|
|
|
|
|
{
|
|
|
|
|
instance = _inactiveInstances.Last.Value;
|
|
|
|
|
RemoveInactiveNode(instance);
|
|
|
|
|
}
|
|
|
|
|
else if (_instancesByObject.Count < _config.capacity)
|
|
|
|
|
{
|
|
|
|
|
instance = CreateInstance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (instance == null)
|
|
|
|
|
{
|
|
|
|
|
Log.Warning($"Pool exhausted for '{_assetPath}'. Capacity: {_config.capacity}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance.IsActive = true;
|
|
|
|
|
_activeCount++;
|
|
|
|
|
TouchPrefab();
|
|
|
|
|
|
|
|
|
|
GameObject gameObject = instance.GameObject;
|
|
|
|
|
if (parent != null)
|
|
|
|
|
{
|
2026-03-26 13:30:57 +08:00
|
|
|
gameObject.transform.SetParent(parent, false);
|
2026-03-26 10:49:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gameObject.SetActive(true);
|
|
|
|
|
return gameObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RuntimePooledInstance CreateInstance()
|
|
|
|
|
{
|
|
|
|
|
GameObject gameObject = GameObject.Instantiate(_prefab);
|
|
|
|
|
gameObject.SetActive(false);
|
|
|
|
|
gameObject.transform.SetParent(_root, false);
|
|
|
|
|
|
|
|
|
|
PooledGameObjectMarker marker = gameObject.GetComponent<PooledGameObjectMarker>();
|
|
|
|
|
if (marker == null)
|
|
|
|
|
{
|
|
|
|
|
marker = gameObject.AddComponent<PooledGameObjectMarker>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var instance = MemoryPool.Acquire<RuntimePooledInstance>();
|
|
|
|
|
instance.Initialize(gameObject, marker);
|
|
|
|
|
marker.Bind(this, instance);
|
|
|
|
|
_instancesByObject.Add(gameObject, instance);
|
|
|
|
|
_service.RegisterOwnedObject(gameObject, this);
|
|
|
|
|
TouchPrefab();
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ParkInactive(RuntimePooledInstance instance)
|
|
|
|
|
{
|
|
|
|
|
if (instance == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance.IsActive = false;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void DestroyInstance(RuntimePooledInstance instance)
|
|
|
|
|
{
|
|
|
|
|
if (instance == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveInstance(instance);
|
|
|
|
|
|
|
|
|
|
if (instance.GameObject != null)
|
|
|
|
|
{
|
|
|
|
|
instance.Marker?.Detach();
|
|
|
|
|
GameObject.Destroy(instance.GameObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MemoryPool.Release(instance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveInstance(RuntimePooledInstance instance)
|
|
|
|
|
{
|
|
|
|
|
GameObject gameObject = instance?.GameObject;
|
|
|
|
|
if (gameObject == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveInactiveNode(instance);
|
|
|
|
|
|
|
|
|
|
if (instance.IsActive)
|
|
|
|
|
{
|
|
|
|
|
instance.IsActive = false;
|
|
|
|
|
_activeCount = Mathf.Max(0, _activeCount - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_instancesByObject.Remove(gameObject);
|
|
|
|
|
_service.UnregisterOwnedObject(gameObject);
|
|
|
|
|
TouchPrefab();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RemoveInactiveNode(RuntimePooledInstance instance)
|
|
|
|
|
{
|
|
|
|
|
if (instance?.InactiveNode == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_inactiveInstances.Remove(instance.InactiveNode);
|
|
|
|
|
instance.InactiveNode = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void TouchPrefab()
|
|
|
|
|
{
|
|
|
|
|
_lastPrefabTouchTime = Time.time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Clear()
|
|
|
|
|
{
|
|
|
|
|
_config = null;
|
|
|
|
|
_assetPath = null;
|
|
|
|
|
_loader = null;
|
|
|
|
|
_service = null;
|
|
|
|
|
_shutdownToken = default;
|
|
|
|
|
_instancesByObject.Clear();
|
|
|
|
|
_inactiveInstances.Clear();
|
2026-03-26 13:30:57 +08:00
|
|
|
_shutdownBuffer.Clear();
|
2026-03-26 10:49:41 +08:00
|
|
|
_root = null;
|
|
|
|
|
_prefab = null;
|
|
|
|
|
_prefabLoadSource = null;
|
|
|
|
|
_activeCount = 0;
|
|
|
|
|
_lastPrefabTouchTime = 0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string SanitizeName(string value)
|
|
|
|
|
{
|
|
|
|
|
return string.IsNullOrWhiteSpace(value)
|
|
|
|
|
? "Unnamed"
|
|
|
|
|
: value.Replace('/', '_').Replace('\\', '_').Replace(':', '_');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|