Skip to content

9. Reporting progress of a big job with coroutines

March 25, 2013

So you’re running this big set of calculations that takes lots of time, and you want to keep the user informed on how it’s going. But there’s a problem. No matter what you print, or draw on the screen, nothing actually appears until the whole job is done. But there’s a solution, something called coroutines. I’m not going to try to explain them in depth, but I want to show you how to use them to solve this problem. (I am aware you could use parameter.watch to keep track of a counter, but there is a cleaner way). To restate the problem, we want to interrupt our big job periodically to put some progress on the screen. With normal programming, this is easy, because your big job can write to the screen whenever it wants, to keep the user up to date. Now you would imagine you could do the same in Codea, ie put a statement in your big job that prints progress regularly. The problem is that it doesn’t get to draw anything until your job is finished, and then all the progress messages arrive on the screen at once, ie they have been stacked up waiting for a chance to print. This is because the all the printing to screen, management of touches, etc, only occur when the draw function gets a turn to run, and that won’t happen until your big job finishes. So all the drawing freezes until then. You can see this if you copy this code into a new project and run it. Copy it from here or here, rather than from below.

function setup()
    parameter.action('Run_without_coroutine',bigJob)
    parameter.action('Run_with_coroutine',WithCoroutine)
end

function WithCoroutine()
    cos=coroutine.create(bigJobCos)
    coroutine.resume(cos)
end

function bigJob()
    output.clear() --clear print output area
    for n=1,10 do
        --time consuming code here
        for j=1,5000000 do  local x=3.1^4.5 end
        print('completed',n)
    end
    print('Job done!')
end

function bigJobCos()
    output.clear() --clear print output area
    progress=0 --used for printing status of job
    for n=1,10 do
        --time consuming code here
        for j=1,5000000 do  local x=3.1^4.5 end
        progress=n
        coroutine.yield()
    end
    progress=-1 --signal end of job
    if cos then cos=nil end --just to be tidy, kill this off
end

function draw()
    if progress then --only report if job is running, ie n is not nil
        if progress>0 then
            print('completed',progress) --print progress of job
            coroutine.resume(cos) --carry on calculating
        elseif progress==-1 then
            print('Job done!')
            progress=nil --to stop any more printing
        end
    end
end

Run this program, then click the first button at top left, to see what I’m talking about. Nothing will happen for a while, then bang!, everything arrives at once. Then try clicking the second button and see the progress reports appear on screen, the way you want them. So how do we do this? Essentially, because we can’t update the screen while our big job is running, we suspend the big job, let Codea carry on – which in this case, means running draw, and get the draw function to get the big job going again after it’s finished drawing. We do it with coroutines, like so. First, you tell Codea you want a new coroutine, and give it a function to run. Then you set it going with the resume command.

    cos=coroutine.create(bigJobCos)
    coroutine.resume(cos)

Codea will now start running the bigJobCos function perfectly normally. When we want to suspend the big job and do a report, we set the value of the progress indicator imaginatively called “progress”, so draw will have something to print, and then we insert this line

   coroutine.yield()

This pauses the bigJobCos function, and lets Codea get on with drawing. Now in the draw function, I have placed some code that is watching for progress.

if progress then --only report if job is running, ie n is not nil
        if progress>0 then
            print('completed',progress) --print progress of job
            coroutine.resume(cos) --carry on calculating
        elseif progress==-1 then
            print('Job done!')
            progress=nil --to stop any more printing
        end
    end

Note how this code only executes if progress is not nil, so a job is running, and it prints the progress message and then resumes the coroutine (the cos in brackets is just the name of the variable I set up to hold the coroutine when I created it). Codea goes back to the big job, exactly where it left off, and continues working there, until the next pause, and so on. When the big job is finished, it sets progress=-1, just to let draw know the job is finished. This is just something I put in, it’s not part of the coroutine stuff. When draw sees this, it prints the end of job message and sets progress to nil so it can ignore it from here on. Note also that when the big job finishes, it sets the coroutine variable cos to nil, to avoid loose ends. So a coroutine involves the following steps

  • create it and give it a function to run
  • resume it, to get it started
  • use coroutine.yield() to pause it
  • use coroutine.resume() to continue where you left off

As with many things in Lua, coroutines can be used for many exotic uses, but this is a very practical and useful one. There’s another nice tutorial here.

Advertisement

From → Physics

4 Comments
  1. Ken Duerden permalink

    That blew my mind for a while but I think I now understand it. Very useful though, thanks for posting.

  2. Inanc permalink

    The code is giving an error because of this line:

    if progress>0 then

    Can you correct it? (I have copied it from this page, I can’t open pastebin.com because it is banned by the f… Government)

  3. Inanc permalink

    Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: