ID:179173
 
I have a couple of recursive functions in my game, and just received this error:

runtime error: Maximum recursion level reached (perhaps there is an infinite loop)
To avoid this safety check, set world.loop_checks=0.


It also crashed the proc and essentially killed my game. I was wondering if setting world.loop_checks would actually increase the recursion allowance at all, or if this message comes right before you exceed the maximum, and regardless of whether the safety check is on or off, the proc will crash when that level is surpassed. (sorry for the run-on sentence). Is there a way to increase the allowed level of recursion? The combination of tiles to produce this effect is rare, so very few games should encounter this, but I don't really have a fix for cutting down the recursion.

Thanks.
Setting loop_checks to false will increase the recursion allowance. Then it depends on the system stack space. There is no way to fine tune it that I know of.

The check is there for safety. However, if you don't have a fix, you might as well try turning it off. You could very well be safe. (Make sure you don't create other loops that are infinite.)
dramstud wrote:
I have a couple of recursive functions in my game, and just received this error:

runtime error: Maximum recursion level reached (perhaps there is an infinite loop)
To avoid this safety check, set world.loop_checks=0.

Usually if recursion is that bad, you actually have hit an infinite loop. Recursion is a tricky beast and sometimes it gets away from you. My best advice would be to post the recursive code here so people can take a look at it; we might be able to suggest a way to eliminate some or most of the recursion you're experiencing.

If all else fails, remember, spawn() is your friend. It will essentially reset the recursion count by doing an action later, which for some types of recursion is all you need.

Lummox JR
In response to Lummox JR
I'm almost 100% certain it was not an infinite loop. Every time I run the function I set a flag on that tile that indicates it has been visited, and these flags are only cleared once the recursion is over with.

I was playing my game trying to test out a fix to a different bug and didn't really pay attention to the fact that I had created an unusually large field (the recursion tracks the entire field). I doubt this will happen in a real game, but I'll still probably set loop checks to False as ACWraith suggested to give myself a little more breathing room.

The code is pretty ugly (maybe 60 lines) and does some tricky stuff that would be confusing with a lot more context, so I don't think I should post it here.

I'll look into spawn() more, but I don't think it will work for me here. In any case, thanks for the advice.
In response to Dramstud
dramstud wrote:
The code is pretty ugly (maybe 60 lines) and does some tricky stuff that would be confusing with a lot more context, so I don't think I should post it here.

I'll look into spawn() more, but I don't think it will work for me here. In any case, thanks for the advice.

I'm willing to take a look at it if you want. If you don't want to post it here, e-mail me at [email protected] and I'll check it out.

My experience with recursion is that most of the time it can be eliminated with clever programming techniques; it's most useful in pseudocode, or when a language has constructs so limited that recursion is about the only graceful solution. Lists are your ally here.

One way to avoid recursion using lists is a loop like this:
todo=things_to_do
while(todo.len)
next=list()
for(thing in todo)
...
if(should recurse)
next+=next_thing
todo=next

Most types of recursion fit nicely into that algorithm. This is also good for cases where you want more processing at higher levels before working down to the lower ones. The only times it's inapplicable are when the lower-level processing absolutely has to be done first, but most recursive processes don't work that way.

Given the size of your loop it's still possible something could trigger a warning here, so spawn() could help anyway.

Lummox JR
In response to Lummox JR
Just to add to Lummox's great advice, here's an old thread that may help you out: [link]

Note also that (in the beta) you can use spawn(-1) to get "instant spawns", which allow you to do safe recursion at large depths without as much timing overhead, assuming you don't need to save the state of the stack.
In response to Tom
Note also that (in the beta) you can use spawn(-1) to get "instant spawns",

Really? I've been doing spawn(0) for this. Is there any real difference?

-AbyssDragon
In response to AbyssDragon
AbyssDragon wrote:
Note also that (in the beta) you can use spawn(-1) to get "instant spawns",

Really? I've been doing spawn(0) for this. Is there any real difference?

spawn() and spawn(0) execute after the current code is finished; spawn(-1) will take up execution immediately and move on to the next block later.
proc/spawn0()
world << "1"
spawn()
world << "3"
world << "2"

proc/spawnMinus1()
world << "1"
spawn(-1)
world << "2"
world << "3"

Lummox JR
In response to Lummox JR
Gotcha. So spawn(-1) doesn't really have any of the properties of spawn() except the fresh call stack.

-AbyssDragon
In response to Lummox JR
Lummox JR wrote:
AbyssDragon wrote:
Note also that (in the beta) you can use spawn(-1) to get "instant spawns",

Really? I've been doing spawn(0) for this. Is there any real difference?

spawn() and spawn(0) execute after the current code is finished; spawn(-1) will take up execution immediately and move on to the next block later.
proc/spawn0()
> world << "1"
> spawn()
> world << "3"
> world << "2"
>
> proc/spawnMinus1()
> world << "1"
> spawn(-1)
> world << "2"
> world << "3"

Lummox JR

While I couldn't use spawn(0), I'm pretty sure spawn(-1) will work for me. From you way you guys described it, it's EXACTLY like using recursion with regards to program flow, except that internally DM handles the stack instead of leaving it up to the OS. Thanks!
In response to AbyssDragon
AbyssDragon wrote:
Gotcha. So spawn(-1) doesn't really have any of the properties of spawn() except the fresh call stack.

Oh, but it does. It's just like reversing the part that's spawned and the part that isn't. If the spawned block goes to sleep or waits for something, the code following it is executed while it waits. For example:
proc/spawnSleepMinus1()
world << "1"
spawn(-1)
world << "2"
sleep(5)
world << "4"
world << "3"

This is opposed to the regular spawn() behavior:
proc/spawnSleep0()
world << "1"
spawn()
world << "3"
world << "2"
sleep(5)
world << "4"

The only real difference is that the spawn block executes first. This is sort of like cooperative multitasking. spawn() in a multithreaded environment would spawn a new thread and both would execute more or less simultaneously, switching back and forth as directed by the operating system. However, BYOND is single-threaded and non-preemptive, so things have to run in order. When spawn() splits a proc into two execution paths, one of them has to go first. The ordinary spawn() or spawn(0) says: "Keep running the proc, but when you get a moment, do this." spawn(-1) says: "Do this now, but when you get a moment, finish the proc."

Lummox JR
In response to Dramstud
dramstud wrote:
While I couldn't use spawn(0), I'm pretty sure spawn(-1) will work for me. From you way you guys described it, it's EXACTLY like using recursion with regards to program flow, except that internally DM handles the stack instead of leaving it up to the OS. Thanks!

Oh, doy, I gave you some misinformation. I was actually talking about spawn(0) (for some reason I thought the notation was spawn(-1)). Lummox gave a very good overview on the difference between the two. Unfortunately, since spawn(-1) does have to preserve the state of the previous function call, it will run into the same recursion limits you would have when calling the function directly. Only calls that separate themselves out completely, like spawn(0), will avoid this limit. The nice thing about spawn(0) is that it is similar to having a soft-coded stack, since the function will execute without any timing delays, as soon as the calling function exits. In many recursion implementations (for example, "flood fill" like schemes such as the example I pointed you to above), this is enough. If your setup is more complicated, this may require a bit of finesse.

[Edit] Oops, I'm wrong again (must not have gotten enough sleep). It turns out that spawn(0), which is the default, executes on the next available tick, rather than processing within the same tick. Damn. I guess immediate queueing was just a "theoretical feature". You'd better stick with a soft-coded stack for now. Sorry for the confusion, boys and girls.