Fusion Object Pooling
Overview
The Fusion Object Pooling
sample aims to showcase how a custom INetworkObjectProvider
implementation can be used to pool
objects and avoid having to instantiate a new prefab for every NetworkObject
spawned by the NetworkRunner
.
With a custom implementation it is possible to keep track and re-use objects, making spawning and despawning more efficient.
Download
Version | Release Date | Download | |
---|---|---|---|
2.0.2 | Oct 21, 2024 | Fusion Object Pooling 2.0.2 Build 707 |
INetworkObjectProvider Interface
Interface which defines the handlers for NetworkRunner
Spawn()
and Despawn()
actions.
Passing an instance of this interface to NetworkRunner.StartGame(Fusion.StartGameArgs)
as the StartGameArgs.ObjectProvider
argument value will assign that instance
as the handler for runner Spawn()
and Despawn()
actions.
Acquire Prefab Instance
The AcquirePrefabInstance
method is called when the NetworkRunner
is spawning a NetworkObject
, it has a NetworkPrefabAcquireContext
and expects a NetworkObject
as output result.
The default implementation behaviour is to simply Instantiate
a new GameObject
.
Release Instance
The ReleaseInstance
method is called when the NetworkRunner
is despawning a NetworkObject
, the method provides
the runner reference and a NetworkObjectReleaseContext
that holds some context information like the NetworkObject
, the
NetworkObjectTypeId
, if it is a nested object and more.
The default implementation behaviour is to simply Destroy
the GameObject
.
Pooling Implementation
This sample uses a custom implementation that will store previous released NetworkObject
s and re-use these already instantiated prefabs when
a new spawn occur.
How to test the sample
Open and run the scene FusionObjectPooling
and press the Start
button, a new session will be started using AutoHostOrClient
as game mode.
After connecting the host should be able to spawn 2 types of objects using the in-game buttons, a cube and a sphere object. Hit Despawn All
and notice how the
objects are disabled instead of destroyed, they are now ready to be used again when a new prefab of their type is spawned.
It is possible to press the Make Pooled Object Transparent
to change the behaviour of pooled object, they will now be set as kinematic and will stay transparent on the game view. This has no real use besides making it easier to see that these objects are free/released.
It is also possible to set a maximum amount of pooled object per type by changing the Max Pool Count
input field, use 0 for unlimited pooled object and any positive number to limit the amount of stored object per type. For instance setting it to 10 will make any extra NetworkObject
released after already having 10 free/pooled/released objects of the same type to be destroyed instead.
Complete Code Snippet
The following is a complete code snippet from this sample's Network Object Provider, but with some few specific logic removed to make it more generic and ready to be used.
C#
public class PoolObjectProvider : Fusion.Behaviour, INetworkObjectProvider
{
/// <summary>
/// If enabled, the provider will delay acquiring a prefab instance if the scene manager is busy.
/// </summary>
[InlineHelp]
public bool DelayIfSceneManagerIsBusy = true;
private Dictionary<NetworkPrefabId, Queue<NetworkObject>> _free = new Dictionary<NetworkPrefabId, Queue<NetworkObject>>();
/// <summary>
/// How many objects are going to be kept on the pools, 0 or negative means to pool all released objects.
/// </summary>
private int _maxPoolCount = 0;
/// The base <see cref="NetworkObjectProviderDefault"/> by default simply instantiate a new game object.
/// Let's create a method to use a custom logic that will pool objects.
protected NetworkObject InstantiatePrefab(NetworkRunner runner, NetworkObject prefab,
NetworkPrefabId contextPrefabId)
{
var result = default(NetworkObject);
// Found free queue for prefab AND the queue is not empty. Return free object.
if (_free.TryGetValue(contextPrefabId, out var freeQ))
{
if (freeQ.Count > 0)
{
result = freeQ.Dequeue();
result.gameObject.SetActive(true);
return result;
}
}
else
{
_free.Add(contextPrefabId, new Queue<NetworkObject>());
}
// -- At this point a free queue was not yet created or was empty. Create new object.
result = Instantiate(prefab);
return result;
}
protected void DestroyPrefabInstance(NetworkRunner runner, NetworkPrefabId prefabId, NetworkObject instance)
{
if (_free.TryGetValue(prefabId, out var freeQ) == false)
{
// No free queue for this prefab. Should be destroyed.
Destroy(instance.gameObject);
return;
}
else if (_maxPoolCount > 0 && freeQ.Count >= _maxPoolCount)
{
// The pool already have the max amount of object we defined. Should be destroyed.
Destroy(instance.gameObject);
return;
}
// Free queue found. Should cache.
freeQ.Enqueue(instance);
// Make objects inactive.
instance.gameObject.SetActive(false);
}
public NetworkObjectAcquireResult AcquirePrefabInstance(NetworkRunner runner, in NetworkPrefabAcquireContext context,
out NetworkObject instance)
{
instance = null;
if (DelayIfSceneManagerIsBusy && runner.SceneManager.IsBusy) {
return NetworkObjectAcquireResult.Retry;
}
NetworkObject prefab;
try {
prefab = runner.Prefabs.Load(context.PrefabId, isSynchronous: context.IsSynchronous);
} catch (Exception ex) {
Log.Error($"Failed to load prefab: {ex}");
return NetworkObjectAcquireResult.Failed;
}
if (!prefab) {
// this is ok, as long as Fusion does not require the prefab to be loaded immediately;
// if an instance for this prefab is still needed, this method will be called again next update
return NetworkObjectAcquireResult.Retry;
}
instance = InstantiatePrefab(runner, prefab, context.PrefabId);
Assert.Check(instance);
if (context.DontDestroyOnLoad) {
runner.MakeDontDestroyOnLoad(instance.gameObject);
} else {
runner.MoveToRunnerScene(instance.gameObject);
}
runner.Prefabs.AddInstance(context.PrefabId);
return NetworkObjectAcquireResult.Success;
}
public void ReleaseInstance(NetworkRunner runner, in NetworkObjectReleaseContext context)
{
var instance = context.Object;
// Only pool prefabs.
if (!context.IsBeingDestroyed) {
if (context.TypeId.IsPrefab) {
DestroyPrefabInstance(runner, context.TypeId.AsPrefabId, instance);
}
else
{
Destroy(instance.gameObject);
}
}
if (context.TypeId.IsPrefab) {
runner.Prefabs.RemoveInstance(context.TypeId.AsPrefabId);
}
}
public void SetMaxPoolCount(int count)
{
_maxPoolCount = count;
}
}
Back to top