Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@

namespace Netcode.Extensions
{
public class NetworkObjectPool : MonoBehaviour
/// <summary>
/// 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
/// </summary>
public class NetworkObjectPool : NetworkBehaviour
{
[SerializeField]
NetworkManager m_NetworkManager;
private static NetworkObjectPool _instance;

public static NetworkObjectPool Singleton=> _instance;

[SerializeField]
List<PoolConfigObject> PooledPrefabsList;
Expand All @@ -19,20 +25,65 @@ public class NetworkObjectPool : MonoBehaviour

Dictionary<GameObject, Queue<NetworkObject>> pooledObjects = new Dictionary<GameObject, Queue<NetworkObject>>();

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++)
{
var prefab = PooledPrefabsList[i].Prefab;
if (prefab != null)
{
Assert.IsNotNull(prefab.GetComponent<NetworkObject>(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component.");
Assert.IsNotNull(
prefab.GetComponent<NetworkObject>(),
$"{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;
}


}
}

Expand All @@ -59,15 +110,12 @@ public NetworkObject GetNetworkObject(GameObject prefab, Vector3 position, Quate
}

/// <summary>
/// Return an object to the pool (and reset them).
/// Return an object to the pool (reset objects before returning).
/// </summary>
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);
}

Expand Down Expand Up @@ -95,15 +143,14 @@ private void RegisterPrefabInternal(GameObject prefab, int prewarmCount)

var prefabQueue = new Queue<NetworkObject>();
pooledObjects[prefab] = prefabQueue;

for (int i = 0; i < prewarmCount; i++)
{
var go = CreateInstance(prefab);
ReturnNetworkObject(go.GetComponent<NetworkObject>(), 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)]
Expand All @@ -115,7 +162,7 @@ private GameObject CreateInstance(GameObject prefab)
/// <summary>
/// This matches the signature of <see cref="NetworkSpawnManager.SpawnHandlerDelegate"/>
/// </summary>
/// <param name="prefabHash"></param>
/// <param name="prefab"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <returns></returns>
Expand All @@ -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;
Expand All @@ -147,12 +193,27 @@ private NetworkObject GetNetworkObjectInternal(GameObject prefab, Vector3 positi
/// <summary>
/// Registers all objects in <see cref="PooledPrefabsList"/> to the cache.
/// </summary>
private void InitializePool()
public void InitializePool()
{
if (m_HasInitialized) return;
foreach (var configObject in PooledPrefabsList)
{
RegisterPrefabInternal(configObject.Prefab, configObject.PrewarmCount);
}
m_HasInitialized = true;
}

/// <summary>
/// Unregisters all objects in <see cref="PooledPrefabsList"/> from the cache.
/// </summary>
public void ClearPool()
{
foreach (var prefab in prefabs)
{
// Unregister Netcode Spawn handlers
NetworkManager.Singleton.PrefabHandler.RemoveHandler(prefab);
}
pooledObjects.Clear();
}
}

Expand All @@ -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);
}
}

}
}