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