Search Unity

Object Pooling - Exception After First Call

Discussion in 'Scripting' started by MrPriest, Oct 22, 2014.

  1. MrPriest

    MrPriest

    Joined:
    Mar 17, 2014
    Posts:
    202
    Hey.
    I've written an object pooler based on the tutorials here. I needed a simple one, and that did the trick.
    Problem is, there are a few scripts calling for an object from the cache. Only the first to arrive gets the object, and the other gets a null reference exception (I'll include the error).
    When the one that did get an object, calls for another object, it works.
    So I might be missing something obvious, or maybe there's some racing condition happening... I'd love for some feedback.
    Thanks!


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class RandomObjectPooler : MonoBehaviour
    6. {
    7.     public GameObject[] pooledObjectsArray;
    8.     public int pooledAmount = 20;
    9.    
    10.     List<GameObject> pooledObjects;
    11.    
    12.     void Start()
    13.     {
    14.         pooledObjects = new List<GameObject>();
    15.         for(int i = 0; i < pooledAmount; i++)
    16.         {
    17.             GameObject obj = (GameObject)Instantiate(pooledObjectsArray[i % pooledObjectsArray.Length]);
    18.             obj.SetActive(false);
    19.             pooledObjects.Add(obj);
    20.         }
    21.     }
    22.    
    23.     public GameObject GetRandomPooledObject()
    24.     {
    25.         while(true)
    26.         {
    27.             int i = Random.Range(0, pooledObjects.Count);
    28.             if(!pooledObjects[i].activeInHierarchy)
    29.             {
    30.                 return pooledObjects[i];
    31.             }
    32.         }
    33.     }
    34. }
    The error:
    "NullReferenceException: Object reference not set to an instance of an object
    RandomObjectPooler.GetRandomPooledObject () (at Assets/_Scripts/Engine/RandomObjectPooler.cs:25)
    TileSpawner.ActivateTile () (at Assets/_Scripts/Engine/TileSpawner.cs:33)
    TileSpawner.Inactive_EnterState () (at Assets/_Scripts/Engine/TileSpawner.cs:21)..."
     
  2. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    why on earth would you use random to find an available go?

    and why are you using a modular operator to to pick a source for your instantiate?
     
  3. Zaladur

    Zaladur

    Joined:
    Oct 20, 2012
    Posts:
    392
    A few things.

    That while loop scares me. If you activate all pool members, and ask for another one, you will never escape it.

    I don't think your lines in the post match up with your actual code lines. Can you identify which line 25 is in your code? While(true) shouldn't be able to throw a nullReference, its gotta be either line 27 or 28.

    Also, an empty list will result in an error, as your Random.Range will return 0 and you will attempt to access pooledObjects[0], which does not exist.
     
  4. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    FYI, a better approach to pooling, is to only have available items in the pool.

    Has two benefits.
    1. You never have to loop through the pool to find the first available item. If there's an item its available.
    2. if the list is empty, you know you need to create a new item.

    I favour the use of a queue<t> for pooling as the insert/removal is quick
     
  5. MrPriest

    MrPriest

    Joined:
    Mar 17, 2014
    Posts:
    202
    Yeah I should have explained what it is supposed to do.
    This is for an infinite runner game.
    I give the pooler the "tiles" (for now, they are four, different colored planes), and then it caches the amount I need from the amount I gave it (so if I know I need more than 4 at the same time, instead of giving it the exact amount in the array, I just tell it to continue instantiating from the given array).
    As for the random, it is so it won't be the same tile every time I move forward.
    BTW I also think a queue is a better option, but from the live training here, Mike (I think) said that it is faster to iterate than to change containers (it also keeps the memory of cached objects consistent).


    The while loop is scary for me as well, but since I know that the spawners will never require more than the available amount, this will do for now.
    As for the lines, sorry, I did not notice they were different. it is line 27 that causes the issue, more specifically, pooledObjects.Count seems to cause it. As if pooledObjects does not exist.

    It is almost like only one spawner can access the pooler, and the rest get a null...

    I will also include the spawner that uses this pooler.
    There are four spawners right now, but only one of them will get the "right" to use the pooler without a null exception...


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using GameEnums;
    5. using GameEngine;
    6.  
    7. public class TileSpawner : FunctionalFiniteStateMachine
    8. {
    9.     private RandomObjectPooler randomObjectPooler;
    10.     private GameObject currentTile;
    11.     private Transform myTileTransform;
    12.    
    13.     void Start()
    14.     {
    15.         randomObjectPooler = GameObject.Find("RandomTilePooler").GetComponent<RandomObjectPooler>();
    16.         currentState = GameEnums.Spawner.State.Inactive;  
    17.     }
    18.    
    19.     void Inactive_EnterState()
    20.     {
    21.         ActivateTile();
    22.         currentState = GameEnums.Spawner.State.Active;  
    23.     }
    24.    
    25.     void Active_Update()
    26.     {
    27.         if(!currentTile.activeInHierarchy)
    28.             currentState = GameEnums.Spawner.State.Inactive;      
    29.     }
    30.    
    31.     void ActivateTile()
    32.     {  
    33.         currentTile = randomObjectPooler.GetRandomPooledObject();
    34.         myTileTransform = currentTile.GetComponent<Transform>();
    35.         myTileTransform.position = transform.position;
    36.         myTileTransform.rotation = transform.rotation;
    37.         currentTile.SetActive(true);
    38.     }
    39. }
     
  6. MrPriest

    MrPriest

    Joined:
    Mar 17, 2014
    Posts:
    202
    Seems like the issue was with the state machines... I am not sure what, but nevermind, I just removed it. (It's just that I dislike "if" inside update, I'd rather split it to states instead)