Foreach() loop disrupted without reason

Here is one of my function:

public void BurnTheLine()
{
    List<Vector2Int> placesThatAlreadyBurnt=new();
    GameObject currentInstantFlame;
    foreach(Vector2 point in points)
    {
        print("POINT:"+point);
        currentInstantFlame= Instantiate(flamePrefab, point, Quaternion.identity);
        instantFlames.Add(currentInstantFlame);
        foreach(BoxSpawned box in boxes)
        {
            if(box.ID==new Vector2Int((int)point.x, (int)point.y))
            {
                Debug.Log(new Vector2Int((int)point.x, (int)point.y));
                if(!placesThatAlreadyBurnt.Contains(box.ID))
                {
                    print(box.ID+"::::::"+(int)point.x+","+(int)point.y);
                    box.boxSpawned.GetComponent<BoxBehaviour>().
                                    Burned(lineDrawer.painter.playerAttributes.burningDuration,
                                                    lineDrawer.painter.playerAttributes.burnDmg);
                    print("TEST");
                    placesThatAlreadyBurnt.Add(box.ID);
                }
                break;
            }
        }
    }
    print("END OF BURNING");
    foreach(GameObject flame in instantFlames)
    {
        StartCoroutine(ExhaustTheFlameAndRemoveThisLine(flame));
    }
}

This code runs to line 18 then jumps straight to line 10 then line 5 then out of the function(visual studio debug mode). Nothing from line 21 has been executed (there still be some more "point"s left). I was seeing the Burned() function, nothing is special here, it’s used normally in some other scripts. Besides, there is no error, no warning, nothing. What is happening here? Thank you for your time and wisdom!

1 Like

Is this a recursive catch-nearby-stuff-on-fire??

If so…

Line 15 is your exit condition test.

Line 18 calls (I presume deeper) into your burn stuff, assuming Burned() leads back into this.

Line 22 writes down the exit condition that this happens.

Pretty sure you want to reverse line 18 and line 22 so “internal” calls detect the end condition.

To understand recursion you must first understand recursion.

3 Likes

Please pardon me, sir. It was my stupidity. The Burned() function is attached to the box’s child, not itself. I will still keep your thoughts in mind, someday it will help me. I still want to know why did it jump out of the loop without error if it was null (in case you have time, sir). I don’t get that it’s a recursion, I didn’t intend to use recursion in any case. This is my Burned() function:

public void Burned(float burningTime, int burnedDmg)
{
    StartCoroutine(IBurning(burningTime, burnedDmg));
}
private IEnumerator IBurning(float burningTime, int burnedDmg)
{
    GameObject thisBoxBurning=Instantiate(burnedFX, transform.position, Quaternion.identity);
    while(burningTime>0)
    {
        yield return new WaitForSeconds(0.05f);
        burningTime-=0.05f;
        TakeDmg(burnedDmg);
    }
    Destroy(thisBoxBurning);
}

BTW, should I delete this post or let it be?
Thank you for your help, kind sir!

You shouldn’t delete your posts after they’ve been resolved. They could end up being helpful to other people in the future, searching for solutions to a problem they’re having that is similar :slightly_smiling_face:

I believe what Kurt meant with his question was “is this an iterative version of a solution that could more naturally be implemented using recursion??”

Recursion vs iteration:

It can look like it at first glance, but I don’t believe it is. The code inside the foreach statement isn’t modifying the contents of points collection itself, only another collection. So while this of course could be written using recursion as well, it would be a boring one, simply calling itself with an argument to get the second point, then the third point, then the fourth point… until reaching the end of the points collection. So just using a loop is the simpler approach in this case.

That is curious :thinking: A couple of potential reasons that come to mind:

  1. The code was executed outside the main thread. In this case it’s easy for errors to get lost to the ether. Code can get executed outside the main thread e.g. via constructors, initializers, OnValidate and OnAfterDeserialize.
  2. There’s a try-catch somewhere in your code, which hides the error.

You could try wrapping the line that caused the MissingReferenceException inside try-catch and then print it out using Debug.LogError, see if that works.

Blockquote

There is it. The debugging gets to it after the loop. So I didn’t understand the code executing consequence well enough. Appreciate it! I’ll try to read the try-catch docs again after this.

1 Like

Be wary of defensive programming that can end up hiding actual errors.

It’s easy to start try-catching and null-checking everything, and aborting method execution silently, thinking that this is a good thing, as it could prevent the game from breaking for a user.

This can however actually be a really bad thing to do in practice, because it can cause errors to be swept under the rug, instead of being fixed as soon as they occur.

It also can make debugging more difficult, because instead of immediately getting an error when something is null, multiple layers of defensive programming may have kicked the can down the road. Then you can end up just putting bandages on side effects, instead of fixing the actual root cause of the issue.

For these reasons, whenever you use try-catch, you should almost always either rethrow the error, or log a warning or error to the Console, to avoid hiding errors:

try
{
    DoSomething();
}
catch(Exception e)
{
    Debug.LogException(e);
}

When you need to make sure that some important clean up step gets performed after a method has finished executing, you can also use try-finally, instead of try-catch:

try
{
    DoSomething();
}
finally
{
   DoSomeImportantCleanUp(); // <- This gets executed even if DoSomething throws an exception
} // <- Exception that occurred inside DoSomething get rethrown here automatically

And a second alternative to try-finally is having an object implement IDisposable, and initializing it inside a using statement, which will automatically call Dispose on said object, when leaving the scope of the statement (even if an exception gets thrown inside the statement):

var using(var obj = new ObjectThatNeedsToBeDisposed())
{
    obj.DoSomething();
} // <- obj.Dispose() is executed here automatically
1 Like