How to make pooling work in a multiplayer game? We want smooth performance.

I see no pooling plugins on the Asset Store that work with Photon or any other networking plugin. Some notable problems that I would think need to be overcome:

  1. RPC calls (at least with Photon) cannot return anything, so you can’t do anything after you activate the game object, such as parent it or whatever because you can’t return a Transform or anything.
  2. No built in list of “game objects by view id” to find the same object on each client and do the same thing to it (activate / deactivate).

Bolt didn’t have either of these problems but unfortunately that’s a very risky product to use at this point (IMHO).

I find it incredibly hard to believe that all the thousands of multiplayer games being made with Unity use the performance-intensive Instantiate and Destroy calls in real-time multiplayer player. That sounds like a recipe for terribly inconsistent frame rate.

Does anyone have any information or has done this before?

Thank you in advance!
-Brian

1 Like

Bolt had a rough time but is back on the tracks. It still is beta but we plan to update every two weeks.
Anyhow.

I answered the questions in the other thread. Please have a look and get back to us by mail or in our forum. That’s just easier for us to support.
It will help if you let us know what your plugin would need to do/message.

Thanks tobiass. Will do.

@jerotas I am glad that we at least could support in Forge with the #2 item since it is easy to look up.

There is a word of caution about the pooling on top of other systems. The instantiate RPC calls are usually buffered, so those objects are then instantiated and stored in a buffer to go to new players that join. That means that there may have to be a core support in a networking system for pooling of objects. Now that you bring this up I think that we can make some useful handles in Forge to allow for pooling to take hold.

The interesting part (as stated) is these objects will have to be pulled to a new player, then they will need to update their status (on or off). After that they can then continue with the normal work flow for pooling. If you would like I would be more than happy to help you with your plugin to do some knowledge sharing on integrating with 3rd parties on our core (such as pooling) and then share what we know about networking. :slight_smile:

Feel free to shoot us a line or send me a PM and I’d be happy to get you connected to our Slack chat (if you are not already) and also get any kind of thing going. It would be great to support some more great Asset Store plugins as a part of Forge! :smile:

I know that you asked a bit about this on our thread, but I wanted to just let you know that we are more than happy to work with you on this process :smile:

Thanks for the response. I will probably buy Forge in the next couple days and will be in touch!

AFAIK UNet supports pooling.

Would be awesome to get concrete proof of that. Link?

I can’t link, it was back in alpha/beta on the groups. I don’t assume it’s changed since though.

Even if it doesn’t… my idea would be to make local pooler gameobject, and then spawn objects locally using either network messages or RPCs. However I am not sure how would I link them together then - I haven’t tested it yet.

i dont know if this is a very efficient method but i used this and so far it worked well:
the functions “SendPlayerCurrentSpawnedAndPooled” are used in case of host migration

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class MasterSpawner : Photon.MonoBehaviour {
    [HideInInspector]
    public List<int> SpawnedObjects = new List<int> ();
    [HideInInspector]
    public List<GameObject> PooledObjects = new List<GameObject> ();
    void OnJoinedRoom()
    {
        if(isMaster)
        BuildPool ();
    }
    public virtual void BuildPool()
    {
    }
    void Awake()
    {
        GetInstance ();
    }
    public static MasterSpawner instance;
    /// <summary>
    /// Gets the instance.
    /// </summary>
    /// <value>The instance.</value>
    public static MasterSpawner Instance
    {
        get
        {
            return instance;
        }
    }
    void GetInstance()
    {
            instance = this;
    }
    void SendPlayerCurrentSpawnedAndPooled(PhotonPlayer playerTarget)
    {
        photonView.RPC ("ReceiveSpawnedObjectList", playerTarget, SpawnedObjects.ToArray ());
        List<int> pooledIDs = new List<int> ();
        foreach (GameObject obj in PooledObjects)
        {
            pooledIDs.Add (FindView (obj));
        }
        photonView.RPC ("ReceivePooledObjectList", playerTarget, pooledIDs.ToArray ());
    }
    [RPC]
    public void ReceiveSpawnedObjectList(int[] toSpawn)
    {
        Debug.Log ("From Master Server: <color=green>"+toSpawn.Length+"  Units spawned</color>");
        foreach (int activeThis in toSpawn)
        {
            FindGO (activeThis).SetActive (true);
        }
    }
    [RPC]
    public void ReceivePooledObjectList(int[] pooled)
    {
        foreach(int obj in pooled)
        {
            FindGO (obj).transform.parent = transform;
            PooledObjects.Add (FindGO (obj));
        }
    }
    /// <summary>
    /// Despawns the object. Must be called only from master server
    /// </summary>
    /// <param name="obj">Object.</param>
    public virtual void DespawnObject(GameObject obj)
    {
        if (!isMaster)
            return;
        int objectID = obj.GetPhotonView ().viewID;
        photonView.RPC ("RPC_DespawnObject", PhotonTargets.AllBufferedViaServer, objectID);
        //[[Updating the list]]
            if(SpawnedObjects.Contains (objectID))
                SpawnedObjects.Remove (objectID);
    }
    public virtual void SpawnObject(GameObject obj, Vector3 spawnPosition)
    {
        if (!isMaster)
            return;
        GameObject NextObjectFound = NextToSpawn (obj); // check null?
        if (!NextObjectFound)
        {
            Debug.Log ("<color=red>Not Enough Object in the Pool</color>");
            return;
        }
        int objectID = NextObjectFound.GetPhotonView ().viewID;
        photonView.RPC ("RPC_SpawnObject", PhotonTargets.AllBufferedViaServer, spawnPosition, objectID);
        //[[Updating the list]]
        SpawnedObjects.Add (objectID);
    }
    [RPC]
    public void RPC_DespawnObject(int ourObjectID)
    {
        FindGO (ourObjectID).SetActive (false);
        FindGO (ourObjectID).transform.position = Vector3.zero;
    }
    [RPC]
    public void RPC_SpawnObject(Vector3 spawnPos, int ourObjectID)
    {
        FindGO (ourObjectID).transform.position = spawnPos;
        FindGO (ourObjectID).SetActive (true);
    }
    /// <summary>
    /// create unit as scene object, add it to pooled list, and immediatly disable it.
    /// </summary>
    /// <returns>The unit.</returns>
    public GameObject PoolUnit(string UnitName)
    {
        GameObject newUnit = PhotonNetwork.InstantiateSceneObject
        (
            UnitName,
            Vector3.zero,
            Quaternion.identity,
            0,
            null
        );
        newUnit.transform.parent = transform;
        PooledObjects.Add (newUnit);
        return newUnit;
    }
    #region PhotonEvents
    void OnPhotonPlayerConnected(PhotonPlayer newPlayer)
    {
        if (isMaster)
            SendPlayerCurrentSpawnedAndPooled (newPlayer);
   
    }
    void OnMasterClientSwitched()
    {
        if (isMaster)
        {Debug.Log ("you are now MasterClient");
            SpawnedObjects.Clear ();
            for(int i =0; i < PooledObjects.Count; i++)
            {
                if(PooledObjects[i].activeSelf)
                    SpawnedObjects.Add (PooledObjects[i].GetPhotonView().viewID);
            }
        }
    }
    #endregion
    #region helper
    private GameObject FindGO(int view_ID)
    {
        PhotonView v = PhotonView.Find (view_ID);
        if (v.viewID != 0)
                return v.gameObject;
        else
                return null;
    }
    private int FindView(GameObject go)
    {
        return go.GetPhotonView ().viewID;
    }
    public bool isMine
    {
        get
        {
            return photonView.isMine;
        }
    }
    public bool isMaster
    {
        get
        {
            return PhotonNetwork.isMasterClient;
        }
    }
    public bool IsSpawned(GameObject _o)
    {
        return SpawnedObjects.Contains (FindView (_o));
    }
    public bool SpawnedLeft()
    {
        return SpawnedObjects.Count > 0;
    }
    private GameObject NextToSpawn(GameObject _type)
    {Debug.Log (PooledObjects.Count);
        int i = 0;
        for (i = 0; i < PooledObjects.Count; i++)
        {
            if(PooledObjects[i].name == _type.name+"(Clone)" && !PooledObjects[i].activeSelf)
                return PooledObjects[i];
        }
        //TODO Case: we dont have enough units in our pool
        return null;
    }
    private GameObject NextToDespawn(GameObject _type)
    {
        GameObject go;
        for (int i = 0; i < SpawnedObjects.Count; i++)
        {
            go = FindGO(SpawnedObjects[i]);
            if(go.name == _type.name+"(Clone)" && !go.activeSelf)
                return go;
        }
        return null;
    }
    #endregion
1 Like

Awesome! :smile: Thanks for the support. I think that we can come up with a new strategy to working with it. It may also be a great segue into having broader support for 3rd party plugins in Forge. I know that @ElroyUnity ported his Asset Store addon to Forge and we developed some extra hooks so that it was more easily possible. However I would like to make it much easier to plugin to any part of the system. :slight_smile: I am looking forward to working with you on this! :smile:

I thought about this and think that pooling should not be a big deal to support well. Unless I miss something, there just has to be some way to replace Instantiate and Destroy for anything networked.
@jerotas : Sent you a PM. Let’s try this.