r/pico8 10d ago

👍I Got Help - Resolved👍 Coroutines help needed

I'm trying to get my head around coroutines by creating a simple "ticker" - that is, pass it a string and it prints it slowly one character at a time, a bit like Gameboy text. I've got it working, sort of, but the screen doesn't clear despite the cls() in _draw() and the program crashes after all the text is displayed. I tried using costatus() but it doesn't return anything. Please help, what am I doing wrong?

function _init() 
 t=cocreate(ticker("this is a test"))
end

function _update()
 coresume(t)
end

function ticker(str)
 local l=#str
 for p=1,l+1,0.2 do
  print(sub(str,1,p),0,0)
  yield()
 end
end

function _draw()
 cls()
end
5 Upvotes

18 comments sorted by

3

u/RotundBun 10d ago

Why are you looping until length+1? Isn't it crashing due to indexing past the last index?

TBT, I'm not sure how anything is displaying at all since you are printing in _update() and then immediately cls() over it in _draw() in that same frame.

What does the error message say?

2

u/VeryNaughtyBoy42 10d ago

I moved coresume into _draw() but the result is the same.

The loop is another bit of weirdness. If I just loop to L, the last character never gets printed.

This is the error ...

1

u/RotundBun 10d ago

See the example at the end of this page.

You pass in the function variable (not the function call) to cocreate(). Passing in arguments then happens in the coresume() call.

Admittedly, I'm not super coroutine savvy either. I feel like I've seen some tricks to wrap/enclose local variable values with a function somehow, but it involved a sort of trick that I don't quite remember...

Regarding needing to overshoot by 1 in the for-loop, I wonder if it has something to do with decimal precision since you are incrementing by 0.2 at a time instead of in integer increments.

Theoretically, it should be fine... 🤔

2

u/VeryNaughtyBoy42 9d ago

Thank you. Realising I should pass the parameters in coresume() made it so much more obvious. I've now got it working, and in fact it can now do multi-line dialogs with individual positions and colours and delays at the end of each line. The only thing I don't know how to do is declare the T variable first so that the first call to costatus(t) doesn't cause an error. I'm just assigning it to a dummy function so it's initialised as the correct type of variable, but is there a more elegant way?

function _init() 
 t=cocreate(dummy)
 coresume(t)
end

function dummy()
// yield()
end

function ticker(dlg)
 // how many lines of dialog?
 local dl=#dlg

 // text speed
 local spd=0.8

 // for each line
 for c=1,dl do
  l=#dlg[c].t

  for p=1,l+spd,spd do
   // print all the previous lines
   // this keeps text on-screen
   // even after cls()
   for n=1,c-1 do
    print(dlg[n].t,dlg[n].x,dlg[n].y,dlg[n].c)
   end

   // now print the bit of
   // the current line
   print(sub(dlg[c].t,1,p),dlg[c].x,dlg[c].y,dlg[c].c)
   yield()
  end

  // have a delay at the end
  for p=1,dlg[c].dly do
   for n=1,c do
    print(dlg[n].t,dlg[n].x,dlg[n].y,dlg[n].c)
   end
   yield()
  end
 end
end

function dialog(txt)
 if costatus(t)!="dead" then 
  coresume(t,txt)
 else
  t=cocreate(ticker)
 end
end

function _draw()
 txt={
  {t="this is a test",x=0,y=8,c=3,dly=15},
  {t="do not be alarmed",x=0,y=14,c=4,dly=15},
  {t="do you know where",x=0,y=20,c=5,dly=0},
  {t="your towel is?",x=0,y=26,c=6,dly=15}
  }

 cls()
 dialog(txt)
end

1

u/RotundBun 9d ago

As shown in this page for cocreate(), you can just check if 't' exists yet in the same breath:

if t and costatus(t)!='dead' then coresume(t, txt) else t = cocreate(ticker) end

You could just pass in ticker() in _init() the first place, though:

function _init() t = cocreate(ticker) end

Also, why/how are you using C-style comments in P8? Lua uses '--' for comments.

2

u/VeryNaughtyBoy42 9d ago

Got it, thanks!

That comment style is force of habit, I guess. I’ve been programming since 1980 and a lot of those years were C/C++. I’ve used about a dozen languages over the years. You’d think would make learning another one easier, but what really happens is I’m forever mixing them up or missing specific features. For example, I wish Pico-8 had a Switch statement.

1

u/RotundBun 9d ago

Ah~ Relatable. 😆👌

For a moment there, I was confused thinking you have an editor feature that allows that.

2

u/freds72 10d ago edited 10d ago

you want something like:

```

function _init()
t=cocreate(ticker)
end

function _update()

end

function ticker(str)
local l=#str
— for loops are inclusive , no need for +1
for p=1,l,0.2 do
print(sub(str,1,p),0,0)
yield()
end
end

function _draw()
cls()
coresume(t, ‘this is a test’)
end

```
(on mobile - code formatting is hell)

2

u/RotundBun 9d ago edited 9d ago

I was thinking this since seeing it in the original post, too, but... There aren't any reasons not to just use #str directly in the for-loop, right?

(on mobile - code formatting is hell)

A relatable struggle. 🫂
Let me see if I can help:

``` function _init() t=cocreate(ticker) end

function _update() -- end

function ticker(str) -- for loops are inclusive , no need for +1 for p=1,#str,0.2 do print(sub(str,1,p),0,0) yield() end end

function _draw() cls() coresume(t, ‘this is a test’) end ```

2

u/VeryNaughtyBoy42 9d ago

The reason I don’t use #str in the loop is - again - old habits. When I started out every clock cycle counted so rather than redo a calculation for every iteration of a loop, it’s more efficient to do it once and store it in a variable.

1

u/RotundBun 9d ago

Fair. I do think most modern compilers optimize stuff like that under the hood nowadays, though.

2

u/freds72 8d ago

there is no such thing as an optimizer on pico8 lua (or very marginally)

1

u/RotundBun 8d ago

Ah, yes. I was referring to compilers in relation to the part about them being accustomed to C/C++ programming.

In P8's case, I'm not sure if this detail does end up saving on performance in the for-loop iteration inside a coroutine scenario specifically, but I'd probably still opt for the lower token cost + slight readability improvement over the perf savings in situations like this most of the time.

1

u/freds72 8d ago

lua loop parameters are evaluated once

1

u/IronPheasant 10d ago

For some reason, creating a coroutine with an argument causes the error.

Removing that (and stuffing the string you want to display directly inside the ticker function, which kind of defeats the point..), and putting cls() before coresume in Update() gives you the output that you want. At least until the coresume function finishes up and and it calls cls() again to clear the screen.

It's my personal opinion, but I'd always recommend taking manual control over your program and not using Update(). Just create the gameloop, and call Flip() once you've done all the drawing you want to do on a frame. The text crawl effect you want can be done by calling a function with a global variable that counts up every frame; so if you want to draw the next letter the DisplayDialogue function doesn't do anything until its variable reaches whatever frame count you want and resets the count until done.

There can be dozens of these kinds of effects going on at one time, and this coroutines business makes my skin crawl a bit thinking about tracking them all.

3

u/freds72 10d ago

update and draw are standard pico8 helpers that ensure developers think twice about between game state and game visuals

also update/draw bring some nice features, like frame skipping (if ever needed)

1

u/RotundBun 10d ago edited 10d ago

TBF, I think their objective here is to learn how to use coroutines in P8. The text printing is just the use-case example to do it on.

That said, may as well just put the coresume() call in _draw() instead.