ID:2155362
 
(See the best response by Ter13.)
Problem description:
So I've been trying to create a complex Ai procedure, and its coming along pretty nice, but unexpectedly, a problem came up that left me with no choice but to turn to the community. Basically, which of these two methods should I use, and if neither, could you tell me why?

Old proc structure
Code:
proc
SpecialAi(var/mob/m)
/*
block of code
*/

if(test==true)
spawn(m.adelay)
SpecialAi(m)
else
return


New proc structure
Code:
proc
SpecialAi(var/mob/m)
while(test==true)
/*
block of code
*/

sleep(m.adelay)



The old proc structure worked fine, but whilst learning C++ I heard that procs calling themselves was a bad idea in most cases so I opted to change it to a while loop, which on the other hand causes severe lag spikes 20+ minutes into gameplay.
You'd want to use the while(), recursive proc calls incur quite a lot more overhead and are far harder to control. It's pretty hard to bail a recursive verb call out at a specific point, but with a loop you always have that option.
There's no significant difference between them (I personally prefer the while), as long as you call the "while" version with spawn so that the caller isn't waiting for it to finish. This is a common issue that beginners run into when using infinite loops:
mob
npc
var
tick_lag

proc
StartAILoop()
for() // this starts an infinite loop; same as while(TRUE)
AIUpdate()
sleep tick_lag

AIUpdate()

player
verb
spawn_blarg_npc()
// Make a new blarg NPC in front of src
var mob/npc/npc = new /mob/npc/blarg (get_step(src, dir))

// Start an infinite loop, woo!
npc.StartAILoop()

// PROBLEM: This message never appears. (until the npc is force-deleted, anyway)
src << "You spawned a new NPC: [npc]!"

The solution is to call the loop with spawn, like so:
spawn npc.StartAILoop()
// (the code before the first `sleep` in StartAILoop()
// is executed when the current proc sleeps or ends)

// or
spawn(-1) npc.StartAILoop()
// (the code before the first `sleep` in StartAILoop()
// is executed before the rest of the current proc)


You might think that the recursive version is creating an increasingly large call stack, but actually, spawn creates a new call stack starting with the proc containing the spawn.

BTW, "return" at the end of a proc, "var/" in proc arguments, and "==true" in a conditional are all unnecessary. Also, this proc has no reason to be global.
Best response
Also, you should avoid spawn() almost completely in your code. It saddles the engine with more overhead than necessary.

All procs have a waitfor setting that determines how return behavior is handled.

Waitfor = 1 (default for most procs) basically means that the calling proc won't receive the return value of the called proc until the proc completely reaches an exit point.

Waitfor = 0 on the other hand will allow the called proc to return a value before it has even completed execution. The return point is the first point as which the called proc encounters a wait of any sort. (sleep or slept call).

I've only found two cases in the last four years where spawn() should actually be used, and both are for delaying a call until pending behavior has had a chance to finish. Essentially, the odds are pretty high that you will never be using spawn() correctly, but one major rule of thumb is that if you are nesting two spawns in a single function, and especially if they are nested, you are using it wrong.
Thanks for your help guys, it really tears me up to know a lot of my program is made up of poor use of syntax though, but you live and you learn, right?
In response to Cheesy pants
Cheesy pants wrote:
Thanks for your help guys, it really tears me up to know a lot of my program is made up of poor use of syntax though, but you live and you learn, right?


I feel you there! A lot of improvements I need to make in my code after learning this. It will be for the better though!