Rogue Tasks

"Tasks" are a neat feature of Rogue. If you tag method with the [task] attribute it becomes an asynchronous method that can be used like a thread. It appears to be concurrent but it is actually executing as a series of updates on the single-threaded main update loop.

Two additional commands can be used inside task methods: 'yield', which pauses execution of the task until the next update, and 'await', which repeatedly yields until an operand task is finished and then evaluates to the return value of that task method.

Calling a task method gives you a "Task" object in return which you can 'await' or 'start'. start()ing a task is like starting a thread; it just starts going. Awaiting a task (only usable from another task, remember) blocks the original task until the second task is complete.

Here's a contrived example of an app initialization class that fetches a JSON list of image URLs from a server, downloads them one at a time, and then loads them as textures. A pre-existing event-based, task-based "HTTPGet" request class is assumed:

class FetchAssets
  PROPERTIES
    asset_list = Asset[]

  METHODS
    method init
      fetch_assets.start

    method go_to_next_state
      # ...

    method fetch_assets [task]
      local list = await fetch_image_list
      if (list?)
        forEach (info in list)
          asset_list.add( Asset(info.url,info.filepath) )
        endForEach

        await download_each_asset
        await load_each_asset
      endIf

      println "All resources loaded"
      go_to_next_state

    method fetch_image_list->PropertyList
      local response = HTTPGet( "http://mygame.com/image_list.json" )
      if (not response.success) return null
      return PropertyList( response.data )

    method download_each_asset [task]
      forEach (asset in asset_list)
        await HTTPGet( asset.url, asset.filepath )
      endForEach

    method load_each_asset [task]
      forEach (asset in asset_list)
        asset.texture = Texture( asset.filepath )
        yield  # only load one per frame
      endForEach
endClass

The technical trick to task methods is that the commands in each task method are turned into an object. Local variables become properties and a big 'which' (switch) essentially keeps track of which command to execute next given whatever yields and awaits have happened.

So the compiler converts this task:

method print_task( first:Integer, last:Integer )->Integer [task]
  local x = first
  while (x <= last)
    println x
    ++x
  endWhile
  return x

into something like this that's automatically hooked into the update loop:

method print_task( first:Integer, last:Integer )->Task
  Task_print_task(first,last)

class Task_print_task
  PROPERTIES
    ip     = 0
    first  : Integer
    last   : Integer
    x      : Integer
    result : Integer

  METHODS
    method init( first, last )

    method execute
      which (ip)
        case 1
          x = first
          ++ip
        case 2
          if (not (x <= last))
            ip = 3
          else
            println x
            ++x
          endIf
        case 3
          result = x
          ip = -1  # signal "finished"
endClass