Log In  

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.

P#137545 2023-11-16 23:34 ( Edited 2023-11-16 23:49)

2

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.

P#137550 2023-11-17 00:54
4

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.
P#137551 2023-11-17 01:33
2

@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.

P#137552 2023-11-17 01:48

You two are clearly smarter then me.

thank you!

P#137579 2023-11-17 19:33

Psst, if you're satisfied, you should mark this resolved. :)

P#137633 2023-11-19 03:26

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2023-12-11 08:30:38 | 0.013s | Q:16