ID:1144435
 
(See the best response by Stephen001.)
Code:
client/var/i

client/proc/test_recursion()
world << ++i

client/test_recursion()
.=..()
world << ++i

client/verb/debug_recursion()
i=0
test_recursion()


Problem description:

Hey everyone, after copy/pasting numerous overrides to test_recursion like above, I found out that the recursion limit is 198. I know it is possible to stop the recursion limit by setting world.loop_checks = 0, but is there another way to exempt proc calls such as .=..() from raising the recursion alarm?

[EDIT] Though, in the case of ..() calls, it would seem recursion checks ought to be turned off by default. So maybe I should transfer this over to the Feature Requests forum.
use set background=1.
That doesn't do it, and it isn't what set background is for.
Why would ..() be exempt from the recursion limit?
In response to Metamorphman
Metamorphman wrote:
That doesn't do it, and it isn't what set background is for.

Really? Cause that's what I've used it for. And it fixed the problem.
In response to Stephen001
Stephen001 wrote:
Why would ..() be exempt from the recursion limit?

Just for that reason, cause it should be :P
I feel parent calls should be seen as an 'appendage' to the current proc, and not a separate call altogether. It seems kind of intuitive.

Dariuc wrote:
Really? Cause that's what I've used it for. And it fixed the problem.

set background stops the check on infinite loops using for and while, but it doesn't stop the limit on the call stack. all set background really does is make a proc wait for other stuff before it loops. that's it.
Well internally I believe a parent call allocates a new stack-frame, so it can still fall foul of the same exhaustion issues a recursive call can.
I'm sorry, but I don't quite understand what that means. :P You'll have to forgive my lack of programming lingo knowledge.

Though, it sounds to me that you're basically raising the same issue I am? Albeit with the proper technical jargons :)
[EDIT: Actually I think I get it now. IMO having a stack-frame exclusive to each call is fine, I just feel that it's a detrimental behavior to add parent calls themselves to that stack]

My problem is that imposing a recursion limit for parent calls is a real breaking point to the whole mechanism of overriding proc definitions and its main purpose, the facilitation of modular behavior. Consider the situation where you have many libraries which all override client/New to implement their own effects and tie it off with a ..() call. It is of course very unlikely, but if you manage to amass enough of these 'modules' to reach the recursion limit, it's all going to be pointless and you'll have to start sticking everything into one call. For this reason I reckon ..() should be the special case.
Best response
Well, when you call a new procedure, it allocates a wedge of memory for the local variables used in that procedure, somewhere to copy any primitive (number, text) values passed in as parameters etc. These wedges of memory are allocated on "the stack" and so are called "stack frames". When the procedure completes, you free up this wedge of memory (either to the OS, like C++ does, or to some shared pool you can re-use, like Java does).

This basically happens for all procedure calls, in all languages on the PC, BYOND also. So if that new procedure call then calls another procedure also, the process is repeated and you now have 2 stack frames allocated. For a lot of code, you'll have multiple stack frames allocated, but not really more than say ... 25 much of the time.

So as you can imagine, in the recursive scenario, you tend to allocate a lot more of these. For a recursive factional function, you end up with input number + 1 stack-frames allocated at most to run it. Lets say (for argument) our stack frame is about 4 KB of memory, not so bad but kinda big. factorial 1 is 8 KB of RAM to computer, factorial 1000 then takes up ~4 MB of RAM to compute, factional 1000000 takes ~4 GB etc

It also happens to take a buttload of time. Hence the need for a stack frame allocation limit at all, to avoid exhausting the system memory and taking forever. The rationale goes, if you are allocating /that/ many frames and not completing, maybe you're caught in infinite recursion or just have some bad programming practices.

So, that's where the recursion limit comes from. In BYOND apparently it's 198 (I have a feeling it's 200, but you're losing 2 to internal calls). In Java, it's usually 32,000 frames. C++ limits the total stack size instead usually, 1 MB on Microsoft C++, 2 MB usually on GCC.

Now the reason I'm rambling on about stack frames etc, is because your parent call, ..(), is "just another proc call". So, it needs a stack frame for the variables passed in, local variables etc too. Hence, it counts to that recursion limit, because the same potential problems (memory use, CPU time etc) apply.

Now practically, I'd be very surprised if you hit that limit with sensible programming and good modularity. You see, deep type hierarchies (where you have like ... /mob/npc/western/alien/david/son ... etc) are generally considered bad practice in themselves, because your object probably inherits a lot of behaviour and variables that honestly, probably don't make sense for it to have.

200 deep of types is a /lot/ too, lets be honest. The deepest type-hierarchies I've seen in Java have all been less than 10 types deep. If there were more, people would rightly be asking if that code was designed correctly.
Thanks for the great response!
In my use case, i'm simply overriding one proc for one type numerous times. I have a init() proc in client/New() that I stick any initialization into and a ..() call. i.e.

client
proc/init()
client/New()
.=..()
init()

// in one code file/module
client
proc/__init_hud()
__init_inventory_hud()
__init_health_bars()
// etc
// do various hud initializations

init()
.=..()
__init_hud()

// in another code file/module
client
proc/__init_character()
// do various character initializations

init()
.=..()
__init_character()

// and so on...


Perhaps I'm just breaking it down too much, but it's very useful to me to have it in separate parts like this.
Are you actually hitting the recursion limit via that? I'd be a little worried about a game who's initialisation process has 200 or so distinct operations the client needs to perform on connection.

From a practical and reading point of view, I'd probably have all of the __init() procedures called in client/New() (or init()) in one file, and have the various __init procedures live where-ever they are most relevant. The reason mostly is it's quite difficult to read the flow of execution of init() via the method you've taken. If the init() procedure lives centrally/homogeneously then potential initialisation ordering bugs should be trivially easy to spot by inspection. This also happens to solve the recursion limit issue, if you've actually managed to hit that somehow (?? lord I hope not).
Haha no I haven't hit it and I don't plan to! :)
This whole topic just occurred out of curiosity.

I did take the execution flow into consideration. At the moment, for initializations that need to happen in a certain order, I just make sure to put them under the same init() override. For most of them it doesn't really make a difference what order they're in so it's fine.

Still though, it would be nice to have a greater degree of control over the recursion limit. world.loop_checks just isn't cutting it
Well, I'd anticipate it's a fairly un-needed thing, to be honest. I can't imagine up a legit use-case for adjusting the recursion limits, that a refactor wouldn't solve (usually with better performance and maintainability, too).