Ive come across a strange behavior in pico-8
Try typing this into pico-8 and running it
::j:: goto j
Works great! every frame it runs, then calls itself and starts over. nothing odd there.
Now try this one:
function _init() a=cocreate(j) coresume(a) end function j() j() end
It should behave the same, right? when the cart is run, it starts the j loop, and in the j loop, it runs its code and calls itself every frame, starting its loop again. just like the goto loop!
BUT, when you run this one, pico-8 runs out of memory! is coresume resuming it every frame? NO! its in the init loop! j() is only STARTED, then it calls itself, then the coroutine should stop because its called itself, meaning it shouldnt be called more then once every frame, yet we run out of memory!
Am i missing something, or is this a bug? someone let me know.
I think the behavior is the same, but the internal memory handling is different.
Unlike executing a goto, a function stores threads (local variables within the function, etc.) in memory.
If you put a function inside this function (recursive processing), the next function will be executed before function
j finishes, so threads will be nested and will be out of memory.
A couple of points:
Calling j() from within the coroutine doesn't interact with the coroutine itself - the coroutine is simply executing j(), and that leads it to call j() again, and so on.
In fact, the behaviour of coresume(cocreate(j)) and just j() isn't any different as far as whether there will be a memory error.
The reason you run out of memory is - indeed - because when you call j() from j(), lua needs - at the very least - to remember where to return to after the call finishes - which costs a bit of memory for every call.
- If you replace "j()" with "return j()", the memory error goes away. This is because lua (unlike many languages) supports tail call optimization, wherein a statement like "return func(args)" is carefully executed by jumping to func directly from the current function, so that when func returns - it goes back to the original caller. The reason this doesn't work for just "func(args)" is that you'd need to discard func's return values before returning from the current function.
@thisismypassword's summary is a good one.
I'll just add that with tail call optimization you don't need a a coroutine for the function to run in an infinite loop, you can just call the function normally.
function j() return j() end j()
Will run forever without running out of memory.
If you do want to use a coroutine you probably want to
yield() before calling
j recursively like so:
function j() yield() return j() end
And then you'd need to call
coresume in the
_update function (or some other loop where you're doing your updating.) I'm not sure if you still need the
return with a coroutine but it certainly doesn't hurt anything to have it.
[Please log in to post a comment]