using System.Collections.Generic;
using UnityEngine;
namespace Normal.Realtime {
///
/// Forked version of RealtimePool that does NOT pool avatar prefabs.
/// Avatars are destroyed normally to allow OnDestroy() to execute properly.
/// All other prefabs are pooled as normal.
///
/// Drop this beside a Realtime component to pool all prefab views managed by that Realtime component.
/// It maintains one pool per prefab. Prefab instances are set to inactive when they're despawned.
///
[RequireComponent(typeof(Realtime))]
public class ForkedRealtimePool : MonoBehaviour, IRealtimePrefabInstantiateDelegate {
///
/// Manages the pool for a specific prefab.
/// Created on demand when a prefab is first spawned.
///
private class Pool {
///
/// The prefab that this pool manages.
///
private readonly GameObject _prefab;
///
/// Instances that are ready to be recycled.
///
private readonly Queue _inactiveInstances = new Queue();
public Pool(GameObject prefab) {
_prefab = prefab;
}
///
/// Creates or recycles a new instance.
///
public GameObject AcquireFromPool() {
if (TryRecycle(out var prefabInstance)) {
return prefabInstance;
} else {
return Instantiate(_prefab);
}
}
///
/// Returns an instance to the pool.
///
public void ReturnToPool(GameObject prefabInstance) {
_inactiveInstances.Enqueue(prefabInstance);
// Deactivate
// Also triggers OnDisable on components
prefabInstance.SetActive(false);
}
///
/// Creates an amount of inactive instances and places them into the pool.
///
public void Preload(int count) {
for (var i = 0; i < count; i++) {
var prefabInstance = Instantiate(_prefab);
ReturnToPool(prefabInstance);
}
}
// InstantiateAsync was backported to 2022.3.20f1.
// But Unity only provides a UNITY_X_Y_OR_NEWER define (not UNITY_X_Y_Z_OR_NEWER).
// So we can't safely compile it for 2022.3 and we use Unity 6 as the cutoff instead.
#if UNITY_6000_0_OR_NEWER
///
/// Creates an amount of inactive instances and places them into the pool using .
///
public AsyncInstantiateOperation PreloadAsync(int count) {
var operation = InstantiateAsync(_prefab, count);
// Handle case where the operation has finished synchronously
if (operation.isDone) {
OnPreloadAsyncComplete(operation);
} else {
operation.completed += OnPreloadAsyncComplete;
}
return operation;
}
///
/// Processes the results of an AsyncInstantiateOperation.
///
private void OnPreloadAsyncComplete(AsyncOperation operation) {
var instantiateOperation = (AsyncInstantiateOperation)operation;
foreach (var result in instantiateOperation.Result) {
var prefabInstance = (GameObject)result;
ReturnToPool(prefabInstance);
}
}
#endif
///
/// Destroys all inactive instances in the pool.
///
public void Clear() {
while (_inactiveInstances.Count > 0) {
var prefabInstance = _inactiveInstances.Dequeue();
// Destroy the GameObject
UnityEngine.Object.Destroy(prefabInstance);
}
}
///
/// Tries to recycle an inactive instance.
///
private bool TryRecycle(out GameObject prefabInstance) {
if (_inactiveInstances.Count == 0) {
prefabInstance = null;
return false;
} else {
prefabInstance = _inactiveInstances.Dequeue();
// Activate
// Also triggers OnEnable on components
prefabInstance.SetActive(true);
return true;
}
}
///
/// Creates a new instance.
///
private GameObject Instantiate(GameObject prefab) {
// Instantiate the prefab
return UnityEngine.Object.Instantiate(prefab);
}
}
///
/// Maps a prefab to its using the prefab's name.
///
private readonly Dictionary _pools = new Dictionary();
///
/// Used to avoid allocation for GetComponents.
///
private static readonly List _tempPoolCallbacks = new List(20);
///
/// When preloading prefabs by name, used to resolve the name to a prefab we can instantiate.
///
private IRealtimePrefabLoadDelegate _prefabLoader;
private void Awake() {
// Use the same one that is being used by Realtime
if (!TryGetComponent(out _prefabLoader)) {
_prefabLoader = new DefaultRealtimePrefabDelegate();
}
}
///
public GameObject InstantiateRealtimePrefab(GameObject prefab) {
var pool = GetOrCreatePool(prefab);
var prefabInstance = pool.AcquireFromPool();
InvokeWillReuseFromPool(prefabInstance);
return prefabInstance;
}
///
public void DestroyRealtimePrefab(GameObject prefabInstance) {
// Check if this is an avatar prefab - if so, destroy it instead of pooling
if (prefabInstance.GetComponent() != null) {
// Invoke callbacks before destroying
InvokeWillReturnToPool(prefabInstance);
// Destroy the avatar normally to allow OnDestroy() to execute
UnityEngine.Object.Destroy(prefabInstance);
return;
}
// For non-avatar prefabs, use normal pooling behavior
var rootView = prefabInstance.GetComponent();
var prefabName = rootView.prefabName;
// Use the opposite order of InstantiateRealtimePrefab:
// first InvokeOnWillReturnToPool and then ReturnToPool (which triggers OnDisable)
InvokeWillReturnToPool(prefabInstance);
var pool = GetPool(prefabName);
pool.ReturnToPool(prefabInstance);
}
///
/// Creates an amount of inactive instances of a prefab and places them into the pool.
///
///
/// Uses the specific used by the that this pool belongs to,
/// or a default implementation if it doesn't have one.
///
public void PreloadPrefab(string prefabName, int count) {
RealtimePrefabMetadata prefabMetadata = new RealtimePrefabMetadata() {
prefabName = prefabName,
};
var prefab = _prefabLoader.LoadRealtimePrefab(prefabMetadata);
PreloadPrefab(prefab, count);
}
///
/// Creates an amount of inactive instances of a prefab and places them into the pool.
///
public void PreloadPrefab(GameObject prefab, int count) {
var pool = GetOrCreatePool(prefab);
pool.Preload(count);
}
#if UNITY_6000_0_OR_NEWER
///
///
/// Creates an amount of inactive instances and places them into the pool using .
///
///
///
/// This method avoids the possibility of dropping frames while preloading instances:
/// Call it for each prefab you'd like to load and wait for all the operations to complete before connecting to a room.
///
///
/// Uses the specific used by the that this pool belongs to,
/// or a default implementation if it doesn't have one.
///
public AsyncInstantiateOperation PreloadPrefabAsync(string prefabName, int count) {
RealtimePrefabMetadata prefabMetadata = new RealtimePrefabMetadata() {
prefabName = prefabName,
};
var prefab = _prefabLoader.LoadRealtimePrefab(prefabMetadata);
return PreloadPrefabAsync(prefab, count);
}
///
///
/// Creates an amount of inactive instances and places them into the pool using .
///
///
///
/// This method avoids the possibility of dropping frames while preloading instances:
/// Call it for each prefab you'd like to load and wait for all the operations to complete before connecting to a room.
///
public AsyncInstantiateOperation PreloadPrefabAsync(GameObject prefab, int count) {
var pool = GetOrCreatePool(prefab);
return pool.PreloadAsync(count);
}
#endif
///
/// Destroys all inactive instances of a prefab.
///
public void Clear(GameObject prefab) {
var prefabName = prefab.name;
var pool = GetPool(prefabName);
// We don't necessarily have a pool for this prefab, ex if InstantiateRealtimePrefab was never called for it
pool?.Clear();
}
///
/// Destroys all inactive instances of all prefabs.
///
public void Clear() {
foreach (var pair in _pools) {
var pool = pair.Value;
pool.Clear();
}
}
///
/// Finds an existing pool for the prefab (by name).
///
private Pool GetPool(string prefabName) {
return _pools[prefabName];
}
///
/// Creates or finds an existing pool for the prefab.
///
private Pool GetOrCreatePool(GameObject prefab) {
var prefabName = prefab.name;
if (_pools.TryGetValue(prefabName, out var pool) == false) {
pool = new Pool(prefab);
_pools[prefabName] = pool;
}
return pool;
}
///
/// Invokes on all components in the prefab instance root
/// deriving from .
///
private void InvokeWillReuseFromPool(GameObject prefabInstance) {
prefabInstance.GetComponents(_tempPoolCallbacks);
foreach (var poolCallback in _tempPoolCallbacks) {
poolCallback.PrefabWillReuseFromPool();
}
}
///
/// Invokes on all components in the prefab instance root
/// deriving from .
///
private void InvokeWillReturnToPool(GameObject prefabInstance) {
prefabInstance.GetComponents(_tempPoolCallbacks);
foreach (var poolCallback in _tempPoolCallbacks) {
poolCallback.PrefabWillReturnToPool();
}
}
}
}