Search code examples
.netrecursionunity-game-engineunityscriptyield

How to force a recursive function to yield skip frames?


How can I force a recursive function to yield frames at run time?

I have tried to place yield functions at different stages of the recursive function.

I don't understand what is happening, because the if condition to print the time every 1000 recursions is ignored and it only yields and prints roughly every 1 million recursions. 90% of the print commands that should be picked up are ignored by the code and don't appear. The runtime only yields every 1-2 minutes, and prints a dozen statements at a time.

This is the fastest version of the code that I have managed to write; if I change the yield statement up or downwards, the runtime sometimes totally freezes and nothing happens for 10 minutes.

Why does it freeze and how can I change it?

//A working 3D floodfill function that reads and writes a boolean voxel array: 

private var maxAbort = 1000000000;

function boundary( x:int, y:int, z:int ): IEnumerator //floodfill algo  
{
    if ( read3DboolArray(x,y,z)==false && bcnt<maxAbort){//pprevent stackoverflow with limit FASTER
        if ( x >= 0 && x < bsizex && y >= 0 && y < bsizey && z >= 0 && z < bsizez)
        {           

            write3DboolArray(x,y,z,true);


            boundary(x+1,y,z);
            boundary(x-1,y,z);      yield WaitForFixedUpdate(); 
            boundary(x,y+1,z);
            boundary(x,y-1,z); // yield WaitForFixedUpdate();   
            boundary(x,y,z+1);
            boundary(x,y,z-1);
        }

        if ( bcnt % 1000== 0 )//mark new start if ended this recursion run
        {
            print(bcnt+ "   ------     " +
                (Time.realtimeSinceStartup-tt));
            bcnt+=1;

            yield WaitForFixedUpdate(); 
        }
    } else return; 
}

It's using the MonoDevelop runtime of Unity3D which is a fork of .NET framework 2.0


Solution

  • Using yield in a method essentially turns the method into a constructor for an iterator. For the iterator to actually do something meaningful, you actually need to iterate over it.

        boundary(x+1,y,z);
        boundary(x-1,y,z);      yield WaitForFixedUpdate(); 
        boundary(x,y+1,z);
        boundary(x,y-1,z); // yield WaitForFixedUpdate();   
        boundary(x,y,z+1);
        boundary(x,y,z-1);
    

    This basically just creates a couple of enumerators, and then throws them away. For a recursive yield to work, you'd have to yield all your results all the way back up to the initial call site.

    Doing this using Yield and Coroutines for this kind of workload seems very inefficient. I would suggest either trying to remodel the algorithm to be iterative (by using a stack for example, which would also help with your apparent stack overflow issues), or to offload this into a separate worker thread.