Coroutines are awesome

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
Jasoco
Inner party member
Posts: 3725
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Coroutines are awesome

Post by Jasoco »

Because of the thread over in Support and Development, I decided to do some testing with the examples for Coroutines on lua.org and they look like they could be really useful. Especially if you need to create a lot of tables in memory and usually have long delays while waiting for them to be created.

Like if you need to create a grid. The bigger it is, the longer it takes to generate. But you shouldn't yield every single time in the loop because you end up making something that might take a few seconds into a half a minute or more. So you only yield every few hundred or thousand or so.

For example:

Code: Select all

function love.load()
    --Create a 5000x5000 grid
    local t = love.timer.getTime()
    grid = {}
    for x = 1, 5000 do
        grid[x] = {}
        for y = 1, 5000 do
            grid[x][y] = 1
        end
    end
    print("Time for non Coroutine Grid", love.timer.getTime() - t)
end
vs.

Code: Select all

function love.load()
    --Set it up for coroutine this time
    total = 0
    grid = {}
    co = coroutine.create(function()
        local t = love.timer.getTime()
        local i = 0
        for x = 1, 5000 do
            grid[x] = {}
            for y = 1, 5000 do
                grid[x][y] = 1
                total = total + 1
                i = i + 1
                if i >= 10000 then
                    coroutine.yield()
                    i = 0
                end
            end
        end
        print("Time for Coroutine Grid", love.timer.getTime() - t)
    end)
end

function love.update(dt)
    coroutine.yield(co)
end

function love.draw()
    love.graphics.print(love.graphics.getFPS() .. "\n" .. total, 10, 10)
end
Now, with a 5,000x5,000 grid that's 25,000,000. So if you bump the yield cutoff up a bit, you can still keep it fast. See, while the grid creation part is fast, the other side may be the bottleneck. So raising the cutoff higher will cut down on how often the other side is processed and give more time per frame to the coroutine and allow it to do its thing faster. I mean 25 million is a lot. So cutting it into smaller chunks will make it go much much faster.

For instance:

Code: Select all

1000 per call:
Time for non Coroutine Grid	0.385
Time for Coroutine Grid	45.052

10000 per call:
Time for non Coroutine Grid	0.385
Time for Coroutine Grid	4.329

100000 per call:
Time for non Coroutine Grid	0.427
Time for Coroutine Grid	0.93
These could be useful for stuff like progressbars too. Loading stuff while drawing the bar. A while ago I was playing with some code for downloading a file from the internet and instead of coroutines it instead just called certain love.run() functions from inside itself. But using a coroutine would be a lot more useful and better. More flexible too. And file downloading can take a while. For instance, when I downloaded some user levels for Mari0, the game froze while large files were downloading. I bet if it were modified to use coroutines it could be much better. No spinning rainbow cursor or hourglass.

Kind of makes me wish stuff loaded slower so I could make really cool loading screens.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: Coroutines are awesome

Post by Robin »

Jasoco wrote:Kind of makes me wish stuff loaded slower so I could make really cool loading screens.
It's a trap!
Help us help you: attach a .love.
User avatar
miko
Party member
Posts: 410
Joined: Fri Nov 26, 2010 2:25 pm
Location: PL

Re: Coroutines are awesome

Post by miko »

Jasoco wrote:Kind of makes me wish stuff loaded slower so I could make really cool loading screens.
While I agree with the above, for file/network loading in chunks you could also use "pumps" like described here:

http://w3.impa.br/~diego/software/luasocket/ltn12.html
http://lua-users.org/wiki/FiltersSourcesAndSinks
My lovely code lives at GitHub: http://github.com/miko/Love2d-samples
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Coroutines are awesome

Post by Inny »

Here's a few uses for coroutines that I find best (mentioned in the other thread):

1. For implementing a state machine in code flow.

In Programming In Lua, Chapter 6.3, an example is given for a very simple text adventure that makes use of tail-calls to keep track of which room the player is in. The problem with such a state machine is that the code is blocking, meaning that while you're in the state machine, no other tasks can be performed without leaving the state machine, meaning either explicit function calls in the state functions, or storing the state in a variable and then exiting from the state functions. Coroutines can simplify this, since you enter the state machine either with coroutine.resume or coroutine.wrap, and exit via coroutine.yield, without needing to store state in a variable.

In the context of Love, that means you don't need to override love.run, you can keep the existing event loop in place, and move into and out of the coroutine-based state machine in love.update. Handy for rougelikes and other oldschool style games.

2. For implementing actor-style sprites.

Another idea, along the same lines, is when you need to implement a hierarchical state machine for managing sprite AI. Those can get quite complex, and lead to very deep and complex class hierarchies. Again, these exist because you don't want code to block, you need to naturally exit functions to continue code flow. What can be done here is that you can give each sprite that has any AI its own coroutines, simplifying the AI into some if-then-else checks in a while loop, and have each sprite yield after making a decision. Also, the coroutines aren't etched in stone, so the sprite can react to external messages via simple regular function calls from the game or other sprites. If the yielded coroutine isn't important, nil it and create a new one for the state the sprite has to move into.

3. Python-style generators

Mentioned in Programming In Lua, Chapter 9.3, coroutine.wrap makes it easy to do generators. For instance, if you needed a list of the 8 surrounding points around a sprite:

Code: Select all

function neighbors(x, y)
  return coroutine.wrap( function()
    coroutine.yield(x-1, y-1)
    coroutine.yield(x, y-1)
    coroutine.yield(x+1, y-1)
    coroutine.yield(x-1, y)
    coroutine.yield(x+1, y)
    coroutine.yield(x-1, y+1)
    coroutine.yield(x, y+1)
    coroutine.yield(x+1, y+1)
  end )
end

for x, y in neighbors( 0, 0 ) do
  print( x, y )
end
So, yes, coroutines are very awesome.

Disclaimer: The standard warnings about garbage collection and dynamic object creation performance hits apply.
kclanc
Citizen
Posts: 89
Joined: Sun Jan 29, 2012 6:39 pm

Re: Coroutines are awesome

Post by kclanc »

Yes! Thank you for pointing this out. Coroutines are the most underused feature in lua. I've encountered a lot of people who refuse to use them, claiming they are error prone and inefficient. Neither of these claims are true when coroutines are used reasonably. I wonder where this coroutine phobia comes from; maybe they are just too different from the style of programming that people are used to.
User avatar
slime
Solid Snayke
Posts: 3129
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Coroutines are awesome

Post by slime »

I have trouble understanding intuitively exactly when and how to use them in my code that would have a meaningful benefit besides just "a different way to think about things", so I haven't really touched them yet. I think it probably stems from a lack of tutorials or concrete examples that are in 'my terms', so to speak.
kclanc
Citizen
Posts: 89
Joined: Sun Jan 29, 2012 6:39 pm

Re: Coroutines are awesome

Post by kclanc »

Of course, don't use something just because it's "a different way to think about things". Use things when they're "a better way to think about things". I think Inny has described the primary situations where coroutines are beneficial.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Coroutines are awesome

Post by bartbes »

Inny wrote: 3. Python-style generators
I'd like to point out that (at least in the example), it's probably easier (better?) to not use coroutines, iterators do get arguments passed to them, so you could easily use those.
User avatar
Xgoff
Party member
Posts: 211
Joined: Fri Nov 19, 2010 4:20 am

Re: Coroutines are awesome

Post by Xgoff »

at least in my case i haven't used coroutines a whole lot because... i usually just don't need them. they're really nice when you DO need them, though.

occasionally i'll use them as generators if lua's normal iterator protocol is insufficient for some purpose
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Coroutines are awesome

Post by Inny »

bartbes wrote:
Inny wrote: 3. Python-style generators
I'd like to point out that (at least in the example), it's probably easier (better?) to not use coroutines, iterators do get arguments passed to them, so you could easily use those.
I will admit that I rarely, if ever, find a place where coroutines would work better than pairs/ipairs.
Post Reply

Who is online

Users browsing this forum: No registered users and 16 guests