WaitWhile Coroutine Does Not Work Properly For WEBGL Builds

I noticed my game, which was working in the Editor, wasn’t properly working in my game build for WebGL, and it was particularly strange that I was receiving a NullReferenceException error in the logs, something that occurred in neither the Editor, nor in the downloadable build.

I rooted around in the code, and after a short amount of troubleshooting, I was able to find what exactly was was returning the error, and it was quite surprising to me what was, since my code specifically ensured this would not happen. Take a look:

IEnumerator Setup()
    {
        //Gives time to unload the current level
        yield return null;

        //Gives time to load the next level
        yield return new WaitWhile(IsTileMapsNull);
       
        // Here tilemaps is null, so when we use it on the following lines, 
        // a null reference exception error occurs.
        tileMaps = GameObject.Find("TileMaps");


        activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;
        inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
        activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
        inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;

        yield break;
    }

Here is the code for IsTileMapsNull()

    bool IsTileMapsNull()
    {
        bool isNull = GameObject.Find("TileMaps") == null;

        if (isNull)
        {
            Debug.Log("waiting");
        }

        return isNull;
    }

The WaitWhile coroutine should ensure that we only pass after once we’ve given the game enough time to load the next scene and confirm the TileMaps object isn’t null.

I really wasn’t sure at all why this was happening, but I did some research and found this * similar issue * happening with async/await builds for WebGL, but, since coroutines don’t use multiple threads, I still was a bit confused. Then, I also researched similar issues of features not properly working due to compression, so I turned that off, built, and still nothing changed. In the end I replaced the previous code with the following version of the code, and it fixed the issue. (The IsTileMapsNull func remained the same in both versions).

IEnumerator Setup()
    {
        Debug.Log("Setup");

        while (true)
        {
            yield return null;

            yield return new WaitWhile(IsTileMapsNull);
           
            var findTilemaps = GameObject.Find("TileMaps");

            if (findTilemaps != null)
                break;
        }

        tileMaps = GameObject.Find("TileMaps");

        Debug.Log($"Are tileMaps null : {tileMaps == null}");

        activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;
        inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
        activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
        inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;

        Debug.Log("TileMaps doene");
        yield break;
    }
    }

So, why did my WaitWhile code did not work on the WebGL build, but did inside of the Unity Editor and in the Windows/Mac/Linux build? Does it have something to do with the WaitWhile() function? With how WebGL works? Or did it have to do with an issue inside of my code? Much appreciated for any answers!

The answer is always the same… ALWAYS!

How to fix a NullReferenceException error

https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

Three steps to success:

  • Identify what is null ← any other action taken before this step is WASTED TIME
  • Identify why it is null
  • Fix that

That may be true… but is that even what is null?

As far as this:

and the fact that you are using all kinds of findy stuff like GameObject.Find(), well, there you have it!

Remember the first rule of GameObject.Find():

Do not use GameObject.Find();

More information: https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

More information: https://discussions.unity.com/t/899843/12

In general, DO NOT use Find-like or GetComponent/AddComponent-like methods unless there truly is no other way, eg, dynamic runtime discovery of arbitrary objects. These mechanisms are for extremely-advanced use ONLY.
If something is built into your scene or prefab, make a script and drag the reference(s) in. That will let you experience the highest rate of The Unity Way™ success of accessing things in your game.

I imagine this is breaking due to some form of non-determinism in Unity that is cropping up in WEBGL.

I agree with Kurt GameObject.Find is really not anything you should use in any capacity. Can you just serialise a direct reference to this component?

In any case you haven’t really pointed out where the null ref occurs.

Ugh … using GameObject.Find(„“) in a coroutine‘s while loop. That hurts!

You should instead have an inspector reference to the Tilemap, done.
If you can‘t think of or find a way to make this work, refactor.
The worst solution is alway, ALWAYS :wink: to try and „find“ something. You know where scene objects are, you don‘t actually need to find them if you properly structure your scene. Even unregistered objects would have some sort of parent container objects in order to foreach(Transform child in transform) every child. Or objects register themselves to well-known (singleton) manager‘s properties (usually to be avoided though if there are other options). Or manager sends events and objects register themselves to listen for events they are interested in. Etc etc.

Yes, I apologize. I should have clarified where the null reference exception occurs, but I can confirm that the TileMaps object is null, which is the cause of the error. I made sure this was the case while I was initially testing, but I should have shared this. I recreated the code and made a new build to share:

IEnumerator Setup()
    {
        //Gives time to unload the current level
        yield return null;

        //Gives time to load the next level
        yield return new WaitWhile(IsTileMapsNull);

        tileMaps = GameObject.Find("TileMaps");

        Debug.Log($"Are tileMaps null : {tileMaps == null}");

        activeHeavenTiles = tileMaps.transform.Find("Heaven Active").gameObject;

        Debug.Log("Does this run?");

        inactiveHeavenTiles = tileMaps.transform.Find("Heaven Inactive").gameObject;
        activeHellTiles = tileMaps.transform.Find("Hell Active").gameObject;
        inactiveHellTiles = tileMaps.transform.Find("Hell Inactive").gameObject;

        yield break;
    }

Here are my logs, multiple ways to view, as I’m not sure which one will work:

Imgur
Google Drive

Hopefully that is sufficient to show that is where the error occurs, since the second debug doesn’t run.

With regards to using GameObject.Find, I can do some additional research to improve performance. However, I can create a script for the tilemaps object, and use an approach that looks more like this:

tileMaps = GameObject.Find("TileMaps").GetComponent<TileMaps>();

        activeHeavenTiles = tileMaps.ActiveHeavenTiles;
        inactiveHeavenTiles = tileMaps.InactiveHeavenTiles;
        activeHellTiles = tileMaps.ActiveHellTiles;
        inactiveHellTiles = tileMaps.InactiveHellTiles;

I am admittedly new to managing multiple scenes, but here is an explanation of my scene management and why I use GameObject.Find(“TileMaps”) over serialization, so if you have a suggestion I would be very open to hearing it so I can improve:

I have a scene called ‘Game’ that contains all of my GameManagers and systems that need to exist for the game to function. One of these managers is the TileController script, which contains the IEnumerator Setup() function. Whenever I need to start a new level, I load in my level scene additively, and get a reference to all the tile maps for the level.

A simple solution I can think of off the top of my head, would be with the TileMaps script idea, with all the references serialized just like above, but instead of finding it, I can make just that script a singleton, since I would never have more than one in any given level. Would that be a sufficient enough solution?

However, besides that, I was still curious as to the reason why my WaitWhile() function doesn’t work properly? I appreciate calling out some of my poor programming practices, since it helps me to get better, however, is that the reason the code doesn’t work? Is it GameObject.Find() that is causing this and if so, why does the build specifically only return this error for the WebGL build, but not for for the Downloadable Build? Was this solely due to my code using GameObject.Find()? Or was it something more?

9267087--1297092--upload_2023-9-1_11-13-4.png
9267087--1297095--upload_2023-9-1_11-13-48.png
9267087--1297098--upload_2023-9-1_11-23-20.png

It does. See my post above about not using GameObject.Find().

This is all very well-studied well-understood best practices.

Don’t use GameObject.Find(). It causes nullrefs. And here we are.