ID:927320
 
Hey guys, I have a few questions about the usage of spawn(), and I figured this is the best place to ask about it.

What is spawn() best used for? What are it's drawbacks? Are there alternatives in some cases?

Most often I use it to run things in the 'background'. I've read byond is single threaded, so things can only run in order regardless.

spawn(-1) proc_name()


I use that a lot for my infinite loops, or loops that run for a long time. I also use it when I want to run a proc passively, but I don't want to wait on it to finish before proceeding.

    proc
spar(mob/user, mob/opp)
if(!timer)
timer = 20
spawn(-1) spar_loop(user, opp)
timer = 20

spar_loop(mob/user, mob/opp)
var/gaining = 0
while(user.timer)
rest_pos()
if(gaining)
sleep(20)
continue
sleep(10)
if(!opp)
break
if(opp.timer)
if(check_cap())
break
sparring = 1
gaining = 1
user.Null_Base += ((Gains/100 * user.BPMod)/1000)
sleep(rand(25, 50))
gaining = 0
sparring = 0


Like that.
Not sure why you set timer = 20 twice. Anyways, you seem to know what it does fairly well. It gives up it's priority to the next process and jumps back in when the time is up. It lets the current process finish, of course. Kind of like this:

Use spawn(2) in Process 1
Process 2 activates
Process 2 finishes, 1 tick used
Process 3 activates
Process 3 finishes, 2 ticks now used
Process 1 comes back in now that 2 ticks are used
Process 1 finishes
Process 4 activates
Process 4 finishes
I think it's weird too, but i'm using a timer proc to count down, and if the timer runs out the loop will break, but it starts if the timeris at 0.
Maybe I could just check if the timer is less than 20?
Im not sure, I'll optimize it a bit later lol.

Is it a bad idea to have a ton of spawned procs running? It's kinda needed for a lot of things.

Also, for infinite loops I'm sure it's a matter of how often the coder would want the loop running, but what's the ideal waiting time between loops?

I generally write sleep(10)
It depends on what you are doing. If you are doing something potentially intensive, then you may want to space out the calls a bit. If you are just checking variable values, there shouldn't be any problem with a 1-tick interval.
spawn() is basically as you've described, in terms of effect. But one thing I would like to clarify, is it doesn't really run "in the background". That's not a terrible analogy for the programmer to use, but it leaves spawn as horribly mis-understood.

spawn() basically says "Do this statement later". As you said, BYOND (as far as executing your DM code goes), is single-threaded. So it will only execute one of your statements at a time, ever. BYOND does this, by having a scheduler, internally, that decides what stuff needs executing next.

The work-flow (in a basic sense) is this:

- Whenever, really, you'll get things put on the queue from clients. Verbs called, spawns encountered in currently running code etc.

- Every tick, the scheduler looks at a list of stuff that needs doing, from the queue. It will look at things like scheduled time to see how "old" the piece of work is, and prioritise accordingly, but never do work for the next tick. So it only does work from "now" (this tick) or "the past" (previous ticks, where stuff is outstanding).
- The piece of code (proc, verb, spawned statement) is allowed to run, until: The statement completes (returns, errors etc), or the statement hits a sleep() or similar procedure that sleeps, like world to world Topic() stuff.
- If the statement hits a sleep, the work goes back in the queue, with a little bit of info added that says "I got to line X".
- If the statement entirely completed, it's removed from the queue.
- Rinse and repeat.

The important thing thus to remember, is a spawn()ed section of code cannot run any earlier than the next return or sleep() operation in the procedure that spawn()ed it.

spawn(-1) is all about that "in the past" stuff, and how I described the scheduler above. If your game is not horribly overloaded, you won't have pieces of work "from the past" in the queue. So you have a spawn(-1) statement, which will introduce that piece of work on tick in the past. The scheduler prioritises "past" work to catch-up, and so you are kinda guaranteed your spawn(-1) will happen next.

... unless you do that twice in a procedure, because then the scheduler had two bits of work for "from the past", and it just kind of has to pick one.
Dunno why, never much liked the lack of control I had over spawn functions. I found that a priority queue did the job and gave me better control.

var
event_schedule/schedule = new/event_schedule()

event_schedule
var
list/events = list()
next_event/next

proc
add_event(var/time,var/obj,var/func,var/arguments)
var/timed_event/nev = new/timed_event(world.time+time,obj,func,arguments)

var/count = 0
var/timed_event/ev

if(events.len>0)
while(count<=events.len)
ev = events[count]

if(ev.time>time)
events.Insert(count,nev)
break

count++
else
events.Add(nev)

if(count==0)

if(next!=null)
del next
next = new/next_event(nev)
return nev

remove_event(var/timed_event/ev)
if(events.len>0)
if(events[1]==ev)
del next
if(events.len>0)
next = new/next_event(events[1])

events.Remove(ev)
ev.canceled()

call_event(var/timed_event/ev)
if(ev)
events.Remove(ev)
ev.call_event()
if(ev==next.event)
del next
if(events.len>0)
next = new/next_event(events[0])

next_event
var
timed_event/event

proc
initiate_countdown()
spawn(src.event.time-world.time)
global.schedule.call_event(src.event)

New(var/timed_event/ev)
src.event = ev
src.initiate_countdown()

timed_event
var
time
datum/call_obj
call_func
list/call_args

proc
canceled()
del src
call_event()
if(call_args)
call(call_obj,call_func)(arglist(call_args))
else
call(call_obj,call_func)()

New(var/nt,var/no,var/nf,var/na)
time = nt
call_obj = no
call_func = nf
call_args = na

cancelable
var
datum/cancel_obj
cancel_func
list/cancel_args

New(var/nt,var/no,var/nf,var/na,var/co,var/cf,var/ca)
..(nt,no,nf,na)
cancel_obj = co
cancel_func = cf
cancel_args = ca

canceled()
if(cancel_args)
call(cancel_obj,cancel_func)(arglist(cancel_args))
else
call(cancel_obj,cancel_func)()
..()
In response to Stephen001
Stephen001 wrote:
You may like this:

http://www.byond.com/developer/Stephen001/EventScheduling

Much appreciated. Never had need for the additional features, but those are pretty nice. Gonna be replacing my half-baked implementation, methinks.
No problem. Do feel free to leave me feedback on the hub forums, or raise issues on the bug / feature trackers there. I should get pretty prompt notification of that.
That cleared up a lot of what I was aiming for, thanks a ton Stephen001, I intend to check out the EventScheduling library now, I get the feeling you have a pretty good grip on this stuff, so that has to be useful.

Again, thanks.