diff --git a/com.community.netcode.extensions/Runtime/NetworkObjectPool/NetworkObjectPool.cs b/com.community.netcode.extensions/Runtime/NetworkObjectPool/NetworkObjectPool.cs index 9ee44852..cd980b2d 100644 --- a/com.community.netcode.extensions/Runtime/NetworkObjectPool/NetworkObjectPool.cs +++ b/com.community.netcode.extensions/Runtime/NetworkObjectPool/NetworkObjectPool.cs @@ -7,10 +7,16 @@ namespace Netcode.Extensions { - public class NetworkObjectPool : MonoBehaviour + /// + /// Object Pool for networked objects, used for controlling how objects are spawned by Netcode. Netcode by default will allocate new memory when spawning new + /// objects. With this Networked Pool, we're using custom spawning to reuse objects. + /// Hooks to NetworkManager's prefab handler to intercept object spawning and do custom actions + /// + public class NetworkObjectPool : NetworkBehaviour { - [SerializeField] - NetworkManager m_NetworkManager; + private static NetworkObjectPool _instance; + + public static NetworkObjectPool Singleton=> _instance; [SerializeField] List PooledPrefabsList; @@ -19,11 +25,41 @@ public class NetworkObjectPool : MonoBehaviour Dictionary> pooledObjects = new Dictionary>(); + private bool m_HasInitialized = false; + public void Awake() + { + if (_instance != null && _instance != this) + { + Destroy(this.gameObject); + } + else + { + _instance = this; + } + } + + public override void OnNetworkSpawn() { InitializePool(); } + public override void OnNetworkDespawn() + { + ClearPool(); + } + + public override void OnDestroy() + { + if (_instance == this) + { + _instance = null; + } + + base.OnDestroy(); + } + + public void OnValidate() { for (var i = 0; i < PooledPrefabsList.Count; i++) @@ -31,8 +67,23 @@ public void OnValidate() var prefab = PooledPrefabsList[i].Prefab; if (prefab != null) { - Assert.IsNotNull(prefab.GetComponent(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component."); + Assert.IsNotNull( + prefab.GetComponent(), + $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component." + ); + + } + + var prewarmCount = PooledPrefabsList[i].PrewarmCount; + if (prewarmCount < 0) + { + Debug.LogWarning($"{nameof(NetworkObjectPool)}: Pooled prefab at index {i.ToString()} has a negative prewarm count! Making it not negative."); + var thisPooledPrefab = PooledPrefabsList[i]; + thisPooledPrefab.PrewarmCount *= -1; + PooledPrefabsList[i] = thisPooledPrefab; } + + } } @@ -59,15 +110,12 @@ public NetworkObject GetNetworkObject(GameObject prefab, Vector3 position, Quate } /// - /// Return an object to the pool (and reset them). + /// Return an object to the pool (reset objects before returning). /// public void ReturnNetworkObject(NetworkObject networkObject, GameObject prefab) { var go = networkObject.gameObject; - - // In this simple example pool we just disable objects while they are in the pool. But we could call a function on the object here for more flexibility. go.SetActive(false); - go.transform.SetParent(transform); pooledObjects[prefab].Enqueue(networkObject); } @@ -95,15 +143,14 @@ private void RegisterPrefabInternal(GameObject prefab, int prewarmCount) var prefabQueue = new Queue(); pooledObjects[prefab] = prefabQueue; - for (int i = 0; i < prewarmCount; i++) { var go = CreateInstance(prefab); ReturnNetworkObject(go.GetComponent(), prefab); } - // Register netcode Spawn handlers - m_NetworkManager.PrefabHandler.AddHandler(prefab, new DummyPrefabInstanceHandler(prefab, this)); + // Register Netcode Spawn handlers + NetworkManager.Singleton.PrefabHandler.AddHandler(prefab, new PooledPrefabInstanceHandler(prefab, this)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -115,7 +162,7 @@ private GameObject CreateInstance(GameObject prefab) /// /// This matches the signature of /// - /// + /// /// /// /// @@ -135,7 +182,6 @@ private NetworkObject GetNetworkObjectInternal(GameObject prefab, Vector3 positi // Here we must reverse the logic in ReturnNetworkObject. var go = networkObject.gameObject; - go.transform.SetParent(null); go.SetActive(true); go.transform.position = position; @@ -147,12 +193,27 @@ private NetworkObject GetNetworkObjectInternal(GameObject prefab, Vector3 positi /// /// Registers all objects in to the cache. /// - private void InitializePool() + public void InitializePool() { + if (m_HasInitialized) return; foreach (var configObject in PooledPrefabsList) { RegisterPrefabInternal(configObject.Prefab, configObject.PrewarmCount); } + m_HasInitialized = true; + } + + /// + /// Unregisters all objects in from the cache. + /// + public void ClearPool() + { + foreach (var prefab in prefabs) + { + // Unregister Netcode Spawn handlers + NetworkManager.Singleton.PrefabHandler.RemoveHandler(prefab); + } + pooledObjects.Clear(); } } @@ -163,25 +224,28 @@ struct PoolConfigObject public int PrewarmCount; } - class DummyPrefabInstanceHandler : INetworkPrefabInstanceHandler + class PooledPrefabInstanceHandler : INetworkPrefabInstanceHandler { GameObject m_Prefab; NetworkObjectPool m_Pool; - public DummyPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) + public PooledPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) { m_Prefab = prefab; m_Pool = pool; } - public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) + NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { - return m_Pool.GetNetworkObject(m_Prefab, position, rotation); + var netObject = m_Pool.GetNetworkObject(m_Prefab, position, rotation); + return netObject; } - public void Destroy(NetworkObject networkObject) + void INetworkPrefabInstanceHandler.Destroy(NetworkObject networkObject) { m_Pool.ReturnNetworkObject(networkObject, m_Prefab); } } + +} }