Asynchronous programming in a natural multi-thread-like syntax, escaping from the callback hell.
npm install haxe-continuationhaxe-continuation
=================


An asynchronous functions is a function that accept its last parameter
as a callback function.
And haxe-continuation is a macro library enables you to invoke and write
asynchronous functions like synchronization functions, and automatically
transform these functions into continuation-passing style (CPS). That means
you can write code looks like multithreading without platform
multithreading support.
@:build(com.dongxiguo.continuation.Continuation.cpsByMeta(":async"))
@:async:
haxe
import com.dongxiguo.continuation.Continuation;
@:build(com.dongxiguo.continuation.Continuation.cpsByMeta(":async"))
class Sample
{
// An asynchronous function without automatical CPS transformation.
static function sleepOneSecond(handler:Void->Void):Void
{
haxe.Timer.delay(handler, 1000);
}
// The magic @:async transforms this function to:
// static function asyncTest(__return:Void->Void):Void
@:async static function asyncTest():Void
{
trace("Start continuation.");
for (i in 0...10)
{
// Magic @await prefix to invoke an asynchronous function.
@await sleepOneSecond();
trace("Run sleepOneSecond " + i + " times.");
}
trace("Continuation is done.");
}
public static function main()
{
asyncTest(function()
{
trace("Handler without continuation.");
});
}
}
`
In CPS functions, @await is a magic word to invoke other
async functions. When calling an asynchronous function with the @await prefix, you need not to explicitly pass a callback
function. Instead, the code after @await will be captured as the callback
function for the callee.
Then you could compile it to JavaScript and run it in Node.js:
`
$ haxe -main Sample -js Sample.js -lib continuation && node Sample.js
Start continuation.
Run sleepOneSecond 0 times.
Run sleepOneSecond 1 times.
Run sleepOneSecond 2 times.
Run sleepOneSecond 3 times.
Run sleepOneSecond 4 times.
Run sleepOneSecond 5 times.
Run sleepOneSecond 6 times.
Run sleepOneSecond 7 times.
Run sleepOneSecond 8 times.
Run sleepOneSecond 9 times.
Continuation is done.
Handler without continuation.
`
You may look into the Sample.js generated by Haxe compiler:
` javascript
Sample.asyncTest = function(__return) {
console.log("Start continuation.");
var __iterator_min = 0;
var __iterator_max = 10;
var __doCount = 0;
var __continue_0;
var __continue_01 = null;
__continue_01 = function() {
if(__iterator_min < __iterator_max) {
if(__doCount++ == 0) do (function(i) {
i;
Sample.sleepOneSecond(function() {
console.log("Run sleepOneSecond " + i + " times.");
__continue_01();
});
})(__iterator_min++); while(--__doCount != 0);
} else {
console.log("Continuation is done.");
__return();
}
};
__continue_0 = __continue_01;
__continue_0();
};
Sample.main = function() {
Sample.asyncTest(function() {
console.log("Handler without continuation.");
});
};
`
You would notice that the for loop and the @await sleepOneSecond() in Haxe source now became an asynchronous recursion of function __continue_01.
Another way is using Continuation.cpsFunction macro to write nested CPS functions:
` haxe
import com.dongxiguo.continuation.Continuation;
class Sample2
{
// An asynchronous function without automatically CPS transformation.
static function sleepOneSecond(handler:Void->Void):Void
{
haxe.Timer.delay(handler, 1000);
}
public static function main()
{
// This magic macro will transform function asyncTest to:
// function asyncTest(__return:Void->Void):Void
Continuation.cpsFunction(function asyncTest():Void
{
trace("Start continuation.");
for (i in 0...10)
{
// Magic @await prefix to invoke an asynchronous function.
@await sleepOneSecond();
trace("Run sleepOneSecond " + i + " times.");
}
trace("Continuation is done.");
});
asyncTest(function()
{
trace("Handler without continuation.");
});
}
}
`
See https://github.com/Atry/haxe-continuation/blob/haxe-3.1/tests/TestContinuation.hx
for more examples.
$3
Another feature in haxe-continuation is forking.
The analogy to multithreaded code is instead of processing a list of items serially (one after the other),
you start a thread for each item, and wait for all threads to return.
This can increase performance by issuing several blocking calls at once,
and serving each one as soon as the results are available.
An example fork:
` haxe
@:build(com.dongxiguo.continuation.Continuation.cpsByMeta(":async"))
class Sample
{
@:async function loadAllFiles(files:Array):Array
{
var output:Array =
[
// Start a separate "thread" for each element in files array
@fork(file in files)
{
// The code block executed by each "thread"
@await loadFile(file);
}
];
// At this point, all sub-threads have finished executing.
return output;
}
}
`
$3
Look at https://github.com/Atry/haxe-continuation/blob/haxe-3.1/tests/TestNode.hx.
The example forks 5 threads, and calls Node.js's asynchronous functions in each thread.
$3
haxe-continuation also provides an utility to wrap CPS functions into Iterators.
For example:
` haxe
using com.dongxiguo.continuation.utils.Generator;
@:build(com.dongxiguo.continuation.Continuation.cpsByMeta(":async"))
class TestGenerator
{
@:async
static function intGenerator(yield:YieldFunction):Void
{
for (i in 1...4)
{
for (j in 1...(i+1))
{
trace('$j * $i =');
@await yield(i * j);
trace("-------");
}
}
}
public static function main()
{
for (i in intGenerator)
{
trace(i);
}
}
}
`
The output:
TestGenerator.hx:47: 1 * 1 =
TestGenerator.hx:59: 1
TestGenerator.hx:49: -------
TestGenerator.hx:47: 1 * 2 =
TestGenerator.hx:59: 2
TestGenerator.hx:49: -------
TestGenerator.hx:47: 2 * 2 =
TestGenerator.hx:59: 4
TestGenerator.hx:49: -------
TestGenerator.hx:47: 1 * 3 =
TestGenerator.hx:59: 3
TestGenerator.hx:49: -------
TestGenerator.hx:47: 2 * 3 =
TestGenerator.hx:59: 6
TestGenerator.hx:49: -------
TestGenerator.hx:47: 3 * 3 =
TestGenerator.hx:59: 9
TestGenerator.hx:49: -------
$3
You can use @await to create coroutines for Unity.
` haxe
// Must compile with haxe -lib continuation -net-lib UnityEngine.dll
import com.dongxiguo.continuation.utils.Generator;
@:nativeGen
@:build(com.dongxiguo.continuation.Continuation.cpsByMeta(":async"))
class MyBehaviour extends unityengine.MonoBehaviour
{
var texture:unityengine.Texture2D;
@:async function downloadAvatar(yield:YieldFunction):unityengine.Texture2D
{
var url = "https://avatars3.githubusercontent.com/u/601530";
var www = new unityengine.WWW(url);
// Wait for download to complete
@await yield(www);
return www.texture;
}
@:async function run(yield:YieldFunction):Void
{
this.texture = @await downloadAvatar(yield);
}
function Start():Void
{
this.StartCoroutine(Generator.toEnumerator(run));
}
}
`
The expression @await downloadAvatar(yield) shows that the things you can await are not only Unity built-in instructions, but also your own asynchronous functions. Thus, haxe-continuation` is more powerful than Unity's native C# coroutines.