├── hxbolts
├── Nothing.hx
├── TaskCancellationException.hx
├── executors
│ ├── TaskExecutor.hx
│ ├── internal
│ │ ├── BackgroundThreadTaskExecutorMessage.hx
│ │ └── BackgroundThreadTaskExecutorWorker.hx
│ ├── ImmediateTaskExecutor.hx
│ ├── Executors.hx
│ ├── CurrentThreadTaskExecutor.hx
│ ├── UiThreadTaskExecutor.hx
│ └── BackgroundThreadTaskExecutor.hx
├── IllegalTaskStateException.hx
├── TaskExt.hx
├── TaskCompletionSource.hx
└── Task.hx
├── demos
├── 01-basic
│ ├── demo.hxml
│ └── org
│ │ └── sample
│ │ └── Demo.hx
├── 03-lime
│ ├── assets
│ │ └── logo.png
│ ├── project.xml
│ └── source
│ │ └── org
│ │ └── sample
│ │ └── App.hx
└── 02-threads-openfl
│ ├── assets
│ ├── Intro.eot
│ ├── Intro.ttf
│ ├── Logo.png
│ ├── Intro.woff
│ └── Intro.svg
│ ├── project.xml
│ └── source
│ └── org
│ └── sample
│ └── App.hx
├── tests
├── .munit
├── test
│ ├── util
│ │ ├── TestException.hx
│ │ └── TimerExecutor.hx
│ ├── TestSuite.hx
│ ├── TestMain.hx
│ ├── TaskExecutorsTest.hx
│ └── TaskTest.hx
├── test.hxml.bak
└── test.hxml
├── .gitignore
├── unit-test.sh
├── haxelib.json
├── LICENSE
├── publish-release.sh
└── README.md
/hxbolts/Nothing.hx:
--------------------------------------------------------------------------------
1 | package hxbolts;
2 |
3 | enum Nothing {
4 | nothing;
5 | }
6 |
--------------------------------------------------------------------------------
/demos/01-basic/demo.hxml:
--------------------------------------------------------------------------------
1 | -lib hxbolts
2 | -main org.sample.Demo
3 | -swf demo.swf
4 | -cmd open demo.swf
5 |
--------------------------------------------------------------------------------
/demos/03-lime/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/restorer/hxbolts/HEAD/demos/03-lime/assets/logo.png
--------------------------------------------------------------------------------
/tests/.munit:
--------------------------------------------------------------------------------
1 | version=2.1.0
2 | src=test
3 | bin=build
4 | report=report
5 | hxml=test.hxml
6 | classPaths=..
7 |
--------------------------------------------------------------------------------
/demos/02-threads-openfl/assets/Intro.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/restorer/hxbolts/HEAD/demos/02-threads-openfl/assets/Intro.eot
--------------------------------------------------------------------------------
/demos/02-threads-openfl/assets/Intro.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/restorer/hxbolts/HEAD/demos/02-threads-openfl/assets/Intro.ttf
--------------------------------------------------------------------------------
/demos/02-threads-openfl/assets/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/restorer/hxbolts/HEAD/demos/02-threads-openfl/assets/Logo.png
--------------------------------------------------------------------------------
/demos/02-threads-openfl/assets/Intro.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/restorer/hxbolts/HEAD/demos/02-threads-openfl/assets/Intro.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /hxbolts.zip
2 | /tests/build
3 | /tests/report
4 | /demos/01-basic/demo.swf
5 | /demos/02-threads-openfl/export
6 | /demos/03-lime/export
7 |
--------------------------------------------------------------------------------
/unit-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd $(dirname "$0")/tests
4 | NOSTRIP="$(cat "./test.hxml" | grep -e '-D [ ]*nostrip')"
5 |
6 | # "strip" in the latest macOS is incompatible with hxcpp
7 | if [ "$NOSTRIP" = "" ] ; then
8 | sed -i.bak '/-cpp /i\
9 | -D nostrip\
10 | ' test.hxml
11 | fi
12 |
13 | haxelib run munit test
14 |
--------------------------------------------------------------------------------
/tests/test/util/TestException.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package util;
10 |
11 | class TestException {
12 | public function new() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/hxbolts/TaskCancellationException.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts;
10 |
11 | class TaskCancellationException {
12 | public function new() {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/haxelib.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hxbolts",
3 | "url": "http://restorer.github.io/hxbolts/",
4 | "license": "BSD",
5 | "tags": ["async", "asynchronous", "bolts", "cross", "promise"],
6 | "description": "Deal with async tasks like a boss. Pure Haxe port of java library named Bolts.",
7 | "version": "1.1.2",
8 | "releasenote": "Support for Haxe 4. Fix samples for Lime 7.",
9 | "contributors": ["restorer"]
10 | }
11 |
--------------------------------------------------------------------------------
/tests/test/TestSuite.hx:
--------------------------------------------------------------------------------
1 | import massive.munit.TestSuite;
2 |
3 | import TaskTest;
4 | import TaskExecutorsTest;
5 |
6 | /**
7 | * Auto generated Test Suite for MassiveUnit.
8 | * Refer to munit command line tool for more information (haxelib run munit)
9 | */
10 | class TestSuite extends massive.munit.TestSuite
11 | {
12 | public function new()
13 | {
14 | super();
15 |
16 | add(TaskTest);
17 | add(TaskExecutorsTest);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/hxbolts/executors/TaskExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | interface TaskExecutor {
12 | public function execute(runnable : Void -> Void) : Void;
13 | public function shutdown() : Void;
14 | }
15 |
--------------------------------------------------------------------------------
/demos/03-lime/project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/hxbolts/IllegalTaskStateException.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts;
10 |
11 | class IllegalTaskStateException {
12 | public var message(default, null) : String;
13 |
14 | public function new(message : String) {
15 | this.message = message;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/hxbolts/executors/internal/BackgroundThreadTaskExecutorMessage.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors.internal;
10 |
11 | enum BackgroundThreadTaskExecutorMessage {
12 | SetWorker(worker : BackgroundThreadTaskExecutorWorker);
13 | Execute(runnable : Void -> Void);
14 | Shutdown;
15 | }
16 |
--------------------------------------------------------------------------------
/hxbolts/executors/ImmediateTaskExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | class ImmediateTaskExecutor implements TaskExecutor {
12 | public function new() {
13 | }
14 |
15 | public function execute(runnable : Void -> Void) : Void {
16 | runnable();
17 | }
18 |
19 | public function shutdown() : Void {
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/test.hxml.bak:
--------------------------------------------------------------------------------
1 | ## Flash 9+
2 | -main TestMain
3 | -lib munit
4 | -lib hamcrest
5 | -cp ..
6 |
7 | -cp test
8 | -swf-version 11
9 | -swf build/as3_test.swf
10 |
11 | --next
12 |
13 | ## JavaScript
14 | -main TestMain
15 | -lib munit
16 | -lib hamcrest
17 | -cp ..
18 |
19 | -cp test
20 | -js build/js_test.js
21 |
22 | --next
23 |
24 | ## Neko
25 | -main TestMain
26 | -lib munit
27 | -lib hamcrest
28 | -cp ..
29 |
30 | -cp test
31 | -neko build/neko_test.n
32 |
33 | --next
34 |
35 | ## CPP
36 | -main TestMain
37 | -lib munit
38 | -lib hamcrest
39 | -cp ..
40 |
41 | -cp test
42 | # -D HXCPP_M64
43 | -cpp build/cpp_test
44 |
--------------------------------------------------------------------------------
/tests/test.hxml:
--------------------------------------------------------------------------------
1 | ## Flash 9+
2 | -main TestMain
3 | -lib munit
4 | -lib hamcrest
5 | -cp ..
6 |
7 | -cp test
8 | -swf-version 11
9 | -swf build/as3_test.swf
10 |
11 | --next
12 |
13 | ## JavaScript
14 | -main TestMain
15 | -lib munit
16 | -lib hamcrest
17 | -cp ..
18 |
19 | -cp test
20 | -js build/js_test.js
21 |
22 | --next
23 |
24 | ## Neko
25 | -main TestMain
26 | -lib munit
27 | -lib hamcrest
28 | -cp ..
29 |
30 | -cp test
31 | -neko build/neko_test.n
32 |
33 | --next
34 |
35 | ## CPP
36 | -main TestMain
37 | -lib munit
38 | -lib hamcrest
39 | -cp ..
40 |
41 | -cp test
42 | # -D HXCPP_M64
43 | -D nostrip
44 | -cpp build/cpp_test
45 |
--------------------------------------------------------------------------------
/hxbolts/executors/internal/BackgroundThreadTaskExecutorWorker.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors.internal;
10 |
11 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
12 | import sys.thread.Thread;
13 | #elseif cpp
14 | import cpp.vm.Thread;
15 | #elseif neko
16 | import neko.vm.Thread;
17 | #elseif java
18 | import java.vm.Thread;
19 | #end
20 |
21 | typedef BackgroundThreadTaskExecutorWorker = {
22 | thread : Thread,
23 | loadFactor : Int,
24 | };
25 |
--------------------------------------------------------------------------------
/tests/test/util/TimerExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package util;
10 |
11 | import hxbolts.executors.TaskExecutor;
12 | import massive.munit.util.Timer;
13 |
14 | class TimerExecutor implements TaskExecutor {
15 | private var delay : Int;
16 |
17 | public function new(delay : Int) {
18 | this.delay = delay;
19 | }
20 |
21 | public function execute(runnable : Void -> Void) : Void {
22 | Timer.delay(runnable, delay);
23 | }
24 |
25 | public function shutdown() : Void {
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/demos/02-threads-openfl/project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/hxbolts/TaskExt.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts;
10 |
11 | #if (flash || nme || openfl || lime)
12 | import hxbolts.executors.UiThreadTaskExecutor;
13 | #end
14 |
15 | #if (cpp || neko || java)
16 | import hxbolts.executors.BackgroundThreadTaskExecutor;
17 | #end
18 |
19 | class TaskExt {
20 | #if (flash || nme || openfl || lime)
21 | private static var _UI_EXECUTOR : UiThreadTaskExecutor = null;
22 | public static var UI_EXECUTOR(get, null) : UiThreadTaskExecutor;
23 |
24 | @:noCompletion
25 | private static function get_UI_EXECUTOR() : UiThreadTaskExecutor {
26 | if (_UI_EXECUTOR == null) {
27 | _UI_EXECUTOR = new UiThreadTaskExecutor();
28 | }
29 |
30 | return _UI_EXECUTOR;
31 | }
32 | #end
33 |
34 | #if (cpp || neko || java)
35 | private static var _BACKGROUND_EXECUTOR : BackgroundThreadTaskExecutor = null;
36 | public static var BACKGROUND_EXECUTOR(get, null) : BackgroundThreadTaskExecutor;
37 |
38 | @:noCompletion
39 | private static function get_BACKGROUND_EXECUTOR() : BackgroundThreadTaskExecutor {
40 | if (_BACKGROUND_EXECUTOR == null) {
41 | _BACKGROUND_EXECUTOR = new BackgroundThreadTaskExecutor(8);
42 | }
43 |
44 | return _BACKGROUND_EXECUTOR;
45 | }
46 | #end
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD License
2 |
3 | For hxbolts software
4 |
5 | Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
6 | Copyright (c) 2015-present, Viachaslau Tratsiak. All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or without modification,
9 | are permitted provided that the following conditions are met:
10 |
11 | * Redistributions of source code must retain the above copyright notice, this
12 | list of conditions and the following disclaimer.
13 |
14 | * Redistributions in binary form must reproduce the above copyright notice,
15 | this list of conditions and the following disclaimer in the documentation
16 | and/or other materials provided with the distribution.
17 |
18 | * Neither the name Facebook nor the names of its contributors may be used to
19 | endorse or promote products derived from this software without specific
20 | prior written permission.
21 |
22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
--------------------------------------------------------------------------------
/tests/test/TestMain.hx:
--------------------------------------------------------------------------------
1 | package ;
2 |
3 | import massive.munit.TestRunner;
4 | import massive.munit.client.HTTPClient;
5 | import massive.munit.client.RichPrintClient;
6 | import massive.munit.client.SummaryReportClient;
7 |
8 | // import massive.munit.client.PrintClient;
9 | // import massive.munit.client.JUnitReportClient;
10 | // import massive.munit.client.SummaryReportClient;
11 |
12 | #if js
13 | import js.Lib;
14 | #end
15 |
16 | class TestMain {
17 | public function new() {
18 | var suites : Array> = [
19 | TestSuite
20 | ];
21 |
22 | #if MCOVER
23 | var client = new mcover.coverage.munit.client.MCoverPrintClient();
24 | var httpClient = new HTTPClient(new mcover.coverage.munit.client.MCoverSummaryReportClient());
25 | #else
26 | var client = new RichPrintClient();
27 | var httpClient = new HTTPClient(new SummaryReportClient());
28 | #end
29 |
30 | var runner : TestRunner = new TestRunner(client);
31 | runner.addResultClient(httpClient);
32 |
33 | runner.completionHandler = completionHandler;
34 | runner.run(suites);
35 | }
36 |
37 | private function completionHandler(successful : Bool) : Void {
38 | try {
39 | #if flash
40 | flash.external.ExternalInterface.call("testResult", successful);
41 | #elseif js
42 | js.Lib.eval("testResult(" + successful + ");");
43 | #elseif sys
44 | Sys.exit(0);
45 | #end
46 | } catch (e : Dynamic) {
47 | }
48 | }
49 |
50 | public static function main() : Void {
51 | new TestMain();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/hxbolts/executors/Executors.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | class Executors {
12 | public static var IMMEDIATE_EXECUTOR(default, null) : TaskExecutor = new ImmediateTaskExecutor();
13 |
14 | private static var _UI_EXECUTOR : TaskExecutor = null;
15 | public static var UI_EXECUTOR(get, null) : TaskExecutor;
16 |
17 | private static var _BACKGROUND_EXECUTOR : TaskExecutor = null;
18 | public static var BACKGROUND_EXECUTOR(get, null) : TaskExecutor;
19 |
20 | @:noCompletion
21 | private static function get_UI_EXECUTOR() : TaskExecutor {
22 | if (_UI_EXECUTOR == null) {
23 | #if (openfl || lime || nme || flash || js)
24 | _UI_EXECUTOR = new UiThreadTaskExecutor();
25 | #else
26 | // Fallback.
27 | _UI_EXECUTOR = IMMEDIATE_EXECUTOR;
28 | #end
29 | }
30 |
31 | return _UI_EXECUTOR;
32 | }
33 |
34 | @:noCompletion
35 | private static function get_BACKGROUND_EXECUTOR() : TaskExecutor {
36 | if (_BACKGROUND_EXECUTOR == null) {
37 | #if (cpp || neko || java)
38 | _BACKGROUND_EXECUTOR = new BackgroundThreadTaskExecutor(8);
39 | #else
40 | // It is just fallback to be able to have the same code for all platforms.
41 | // It doesn't mean that, say, javascript will have real background thread.
42 | _BACKGROUND_EXECUTOR = UI_EXECUTOR;
43 | #end
44 | }
45 |
46 | return _BACKGROUND_EXECUTOR;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/hxbolts/executors/CurrentThreadTaskExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
12 | import sys.thread.Mutex;
13 | #elseif cpp
14 | import cpp.vm.Mutex;
15 | #elseif neko
16 | import neko.vm.Mutex;
17 | #elseif java
18 | import java.vm.Mutex;
19 | #end
20 |
21 | class CurrentThreadTaskExecutor implements TaskExecutor {
22 | #if (cpp || neko || jave)
23 | private var runnableQueueMutex : Mutex = new Mutex();
24 | #end
25 |
26 | private var runnableQueue : List Void> = new List Void>();
27 |
28 | public function new() {
29 | }
30 |
31 | public function execute(runnable : Void -> Void) : Void {
32 | #if (cpp || neko || jave)
33 | runnableQueueMutex.acquire();
34 | #end
35 |
36 | runnableQueue.add(runnable);
37 |
38 | #if (cpp || neko || jave)
39 | runnableQueueMutex.release();
40 | #end
41 | }
42 |
43 | public function tick() : Void {
44 | #if (cpp || neko || jave)
45 | runnableQueueMutex.acquire();
46 | #end
47 |
48 | if (runnableQueue.isEmpty()) {
49 | #if (cpp || neko || jave)
50 | runnableQueueMutex.release();
51 | #end
52 |
53 | return;
54 | }
55 |
56 | var queue = Lambda.list(runnableQueue);
57 | runnableQueue.clear();
58 |
59 | #if (cpp || neko || jave)
60 | runnableQueueMutex.release();
61 | #end
62 |
63 | var runnable : Void -> Void;
64 |
65 | while ((runnable = queue.pop()) != null) {
66 | runnable();
67 | }
68 | }
69 |
70 | public function shutdown() : Void {
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/hxbolts/executors/UiThreadTaskExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | #if openfl
12 | import openfl.Lib;
13 | import openfl.events.Event;
14 | #elseif lime
15 | import lime.app.Application;
16 | #elseif nme
17 | import nme.Lib;
18 | import nme.events.Event;
19 | #elseif flash
20 | import flash.Lib;
21 | import flash.events.Event;
22 | #else
23 | import haxe.Timer;
24 | #end
25 |
26 | class UiThreadTaskExecutor extends CurrentThreadTaskExecutor {
27 | #if (!openfl && !lime && !nme && !flash)
28 | private var tickTimer : Timer;
29 | #end
30 |
31 | public function new() {
32 | super();
33 |
34 | #if openfl
35 | Lib.current.stage.addEventListener(Event.ENTER_FRAME, onNextFrame);
36 | #elseif lime
37 | Application.current.onUpdate.add(onNextFrame);
38 | #elseif (nme || flash)
39 | // it is not an error - same line of code as for "openfl"
40 | Lib.current.stage.addEventListener(Event.ENTER_FRAME, onNextFrame);
41 | #else
42 | tickTimer = new Timer(Std.int(1000 / 30));
43 | tickTimer.run = onNextFrame;
44 | #end
45 | }
46 |
47 | private function onNextFrame(#if (openfl || lime || nme || flash) _ #end) : Void {
48 | tick();
49 | }
50 |
51 | override public function shutdown() : Void {
52 | #if openfl
53 | Lib.current.stage.removeEventListener(Event.ENTER_FRAME, onNextFrame);
54 | #elseif lime
55 | Application.current.onUpdate.remove(onNextFrame);
56 | #elseif (nme || flash)
57 | // it is not an error - same line of code as for "openfl"
58 | Lib.current.stage.removeEventListener(Event.ENTER_FRAME, onNextFrame);
59 | #else
60 | tickTimer.stop();
61 | #end
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/publish-release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd $(dirname "$0")
4 | BRANCH="$(git rev-parse --abbrev-ref HEAD)"
5 |
6 | if [ "$BRANCH" == "HEAD" ] ; then
7 | echo "Publishing is not allowed from \"detached HEAD\""
8 | echo "Switch to \"master\", \"develop\" or other valid branch and retry"
9 | exit
10 | fi
11 |
12 | if [ "$(git status -s)" != "" ] ; then
13 | echo "Seems that you have uncommitted changes. Commit and push first, than publish."
14 | git status -s
15 | exit
16 | fi
17 |
18 | if [ "$(git log --format=format:%H origin/${BRANCH}..${BRANCH})" != "" ] ; then
19 | echo "Seems that you have unpushed changes. Pull/push first, than publish."
20 | git log --format=format:"%C(auto)%H %C(green)%an%C(reset) %s" "origin/${BRANCH}..${BRANCH}"
21 | exit
22 | fi
23 |
24 | VERSION="$(cat "./haxelib.json" | grep -e '^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"[0-9.]*"[[:space:]]*,[[:space:]]*$' | sed 's/[^0-9.]//g')"
25 | ESCAPED_VERSION="$(echo "$VERSION" | sed 's/\./\\./g')"
26 | HAS_TAG="$(git tag | grep -e "^v${ESCAPED_VERSION}$")"
27 |
28 | if [ "$HAS_TAG" != "" ] ; then
29 | if [ "$1" == "--retag" ] || [ "$2" == "--retag" ] ; then
30 | git tag -d "v${VERSION}"
31 | git push origin ":v${VERSION}"
32 | else
33 | echo "Git tag v${VERSION} already exists. If you want to recreate tag, use:"
34 | echo "$0 --retag"
35 | exit
36 | fi
37 | fi
38 |
39 | [ -e hxbolts.zip ] && rm hxbolts.zip
40 | [ -e tests/build ] && rm -r tests/build
41 | [ -e tests/report ] && rm -r tests/report
42 | [ -e demos/01-basic/demo.swf ] && rm demos/01-basic/demo.swf
43 | [ -e demos/02-threads-openfl/export ] && rm -r demos/02-threads-openfl/export
44 | [ -e demos/03-lime/export ] && rm -r demos/03-lime/export
45 |
46 | zip -r -9 hxbolts.zip * -x publish-release.sh -x unit-test.sh
47 |
48 | if [ "$1" == "--dry-run" ] || [ "$2" == "--dry-run" ] ; then
49 | exit
50 | fi
51 |
52 | echo "Tagging v${VERSION} ..."
53 | git tag "v${VERSION}" && git push --tags
54 |
55 | echo "Submitting to haxelib ..."
56 | [ -e hxbolts.zip ] && haxelib submit hxbolts.zip
57 | [ -e hxbolts.zip ] && rm hxbolts.zip
58 |
--------------------------------------------------------------------------------
/demos/01-basic/org/sample/Demo.hx:
--------------------------------------------------------------------------------
1 | package org.sample;
2 |
3 | import haxe.Json;
4 | import haxe.Timer;
5 | import hxbolts.Nothing;
6 | import hxbolts.Task;
7 | import hxbolts.TaskCompletionSource;
8 |
9 | using StringTools;
10 |
11 | class Demo {
12 | private static var deletedCommentsIds = new Array();
13 |
14 | private static function makeRequestAsync(url : String, callback : String -> Void) : Void {
15 | var result : String;
16 |
17 | if (url == "http://blog.tld/api/authorize?user=me") {
18 | result = "{\"token\":\"12345\"}";
19 | } else if (url == "http://blog.tld/api/postsIds?token=12345") {
20 | result = "[1,2,3]";
21 | } else if (url.startsWith("http://blog.tld/api/commentsIds?token=12345&postId=")) {
22 | var prefix = url.substr("http://blog.tld/api/commentsIds?token=12345&postId=".length);
23 | result = '[${prefix}1,${prefix}2,${prefix}3]';
24 | } else if (url.startsWith("http://blog.tld/api/deleteComment?token=12345&commentId=")) {
25 | deletedCommentsIds.push(Std.parseInt(url.substr("http://blog.tld/api/deleteComment?token=12345&commentId=".length)));
26 | result = "true";
27 | } else {
28 | result = "false";
29 | }
30 |
31 | Timer.delay(function() : Void {
32 | callback(result);
33 | }, Math.floor(Math.random() * 50) + 10);
34 | }
35 |
36 | private static function makeRequestTask(url : String) : Task {
37 | var tcs = new TaskCompletionSource();
38 |
39 | makeRequestAsync(url, function(result : String) : Void {
40 | tcs.setResult(result);
41 | });
42 |
43 | return tcs.task;
44 | }
45 |
46 | public static function process() : Void {
47 | var token : String = null;
48 |
49 | makeRequestTask("http://blog.tld/api/authorize?user=me").onSuccessTask(function(task : Task) : Task {
50 | token = Reflect.field(Json.parse(task.result), "token");
51 | return makeRequestTask('http://blog.tld/api/postsIds?token=${token}');
52 | }).onSuccessTask(function(task : Task) : Task> {
53 | var tasks = new Array>();
54 |
55 | for (id in (cast Json.parse(task.result) : Array)) {
56 | tasks.push(makeRequestTask('http://blog.tld/api/commentsIds?token=${token}&postId=${id}'));
57 | }
58 |
59 | return Task.whenAllResult(tasks);
60 | }).onSuccessTask(function(task : Task>) : Task {
61 | var tasks = new Array>();
62 |
63 | for (response in task.result) {
64 | for (id in (cast Json.parse(response) : Array)) {
65 | tasks.push(makeRequestTask('http://blog.tld/api/deleteComment?token=${token}&commentId=${id}'));
66 | }
67 | }
68 |
69 | return Task.whenAll(tasks);
70 | }).continueWith(function(task : Task) : Nothing {
71 | if (task.isSuccessed) {
72 | trace("Everything is good");
73 | trace(deletedCommentsIds);
74 | } else {
75 | trace("Error occurred : " + Std.string(task.error));
76 | }
77 |
78 | return null;
79 | });
80 | }
81 |
82 | public static function main() : Void {
83 | process();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/hxbolts/TaskCompletionSource.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Original java implementation:
3 | * Copyright (c) 2014, Facebook, Inc.
4 | * All rights reserved.
5 | *
6 | * Haxe version:
7 | * Copyright (c) 2015, Viachaslau Tratsiak.
8 | * All rights reserved.
9 | *
10 | * This source code is licensed under the BSD-style license found in the
11 | * LICENSE file in the root directory of this source tree. An additional grant
12 | * of patent rights can be found in the PATENTS file in the original project repo:
13 | * https://github.com/BoltsFramework/Bolts-Android/
14 | *
15 | */
16 | package hxbolts;
17 |
18 | @:access(hxbolts.Task)
19 | class TaskCompletionSource {
20 | public var task(default, null) : Task;
21 |
22 | public function new() {
23 | this.task = new Task();
24 | }
25 |
26 | public function trySetResult(value : TResult) : Bool {
27 | #if (cpp || neko || java)
28 | task.mutex.acquire();
29 | #end
30 |
31 | if (task._isCompleted) {
32 | #if (cpp || neko || java)
33 | task.mutex.release();
34 | #end
35 |
36 | return false;
37 | }
38 |
39 | task._isCompleted = true;
40 | task._result = value;
41 | task.runContinuations();
42 |
43 | #if (cpp || neko || java)
44 | task.mutex.release();
45 | #end
46 |
47 | return true;
48 | }
49 |
50 | public function trySetError(value : Dynamic = null) : Bool {
51 | #if (cpp || neko || java)
52 | task.mutex.acquire();
53 | #end
54 |
55 | if (task._isCompleted) {
56 | #if (cpp || neko || java)
57 | task.mutex.release();
58 | #end
59 |
60 | return false;
61 | }
62 |
63 | task._isCompleted = true;
64 | task._isFaulted = true;
65 | task._error = value;
66 | task.runContinuations();
67 |
68 | #if (cpp || neko || java)
69 | task.mutex.release();
70 | #end
71 |
72 | return true;
73 | }
74 |
75 | public function trySetCancelled() : Bool {
76 | #if (cpp || neko || java)
77 | task.mutex.acquire();
78 | #end
79 |
80 | if (task._isCompleted) {
81 | #if (cpp || neko || java)
82 | task.mutex.release();
83 | #end
84 |
85 | return false;
86 | }
87 |
88 | task._isCompleted = true;
89 | task._isCancelled = true;
90 | task.runContinuations();
91 |
92 | #if (cpp || neko || java)
93 | task.mutex.release();
94 | #end
95 |
96 | return true;
97 | }
98 |
99 | public function setResult(value : TResult) : Void {
100 | if (!trySetResult(value)) {
101 | throw new IllegalTaskStateException("Cannot set the result of a completed task.");
102 | }
103 | }
104 |
105 | public function setError(value : Dynamic = null) : Void {
106 | if (!trySetError(value)) {
107 | throw new IllegalTaskStateException("Cannot set the error on a completed task.");
108 | }
109 | }
110 |
111 | public function setCancelled() : Void {
112 | if (!trySetCancelled()) {
113 | throw new IllegalTaskStateException("Cannot cancel a completed task.");
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/hxbolts/executors/BackgroundThreadTaskExecutor.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package hxbolts.executors;
10 |
11 | import hxbolts.executors.internal.BackgroundThreadTaskExecutorMessage;
12 | import hxbolts.executors.internal.BackgroundThreadTaskExecutorWorker;
13 |
14 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
15 | import sys.thread.Mutex;
16 | import sys.thread.Thread;
17 | #elseif cpp
18 | import cpp.vm.Mutex;
19 | import cpp.vm.Thread;
20 | #elseif neko
21 | import neko.vm.Mutex;
22 | import neko.vm.Thread;
23 | #elseif java
24 | import java.vm.Mutex;
25 | import java.vm.Thread;
26 | #end
27 |
28 | class BackgroundThreadTaskExecutor implements TaskExecutor {
29 | private var mutex : Mutex = new Mutex();
30 | private var workerPool : Array = [];
31 |
32 | public function new(poolSize : Int) {
33 | if (poolSize < 1) {
34 | throw "poolSize must be >= 1";
35 | }
36 |
37 | for (i in 0 ... poolSize) {
38 | workerPool.push({
39 | thread: null,
40 | loadFactor: 0,
41 | });
42 | }
43 | }
44 |
45 | private function workerLoop() : Void {
46 | var worker : BackgroundThreadTaskExecutorWorker = null;
47 |
48 | while (true) {
49 | var message : BackgroundThreadTaskExecutorMessage = Thread.readMessage(true);
50 |
51 | switch (message) {
52 | case SetWorker(_worker):
53 | worker = _worker;
54 |
55 | case Execute(runnable): {
56 | runnable();
57 |
58 | var shouldShutdown = false;
59 |
60 | mutex.acquire();
61 | worker.loadFactor--;
62 |
63 | if (worker.loadFactor <= 0) {
64 | shouldShutdown = true;
65 | worker.thread = null;
66 | }
67 |
68 | mutex.release();
69 |
70 | if (shouldShutdown) {
71 | break;
72 | }
73 | }
74 |
75 | case Shutdown:
76 | break;
77 | }
78 | }
79 | }
80 |
81 | public function execute(runnable : Void -> Void) : Void {
82 | var selectedWorker : BackgroundThreadTaskExecutorWorker = null;
83 | var minLoadFactor : Int = 0;
84 |
85 | mutex.acquire();
86 |
87 | for (worker in workerPool) {
88 | if (selectedWorker == null || worker.loadFactor < minLoadFactor) {
89 | selectedWorker = worker;
90 | minLoadFactor = worker.loadFactor;
91 | }
92 | }
93 |
94 | selectedWorker.loadFactor++;
95 |
96 | if (selectedWorker.thread == null) {
97 | selectedWorker.thread = Thread.create(workerLoop);
98 | selectedWorker.thread.sendMessage(BackgroundThreadTaskExecutorMessage.SetWorker(selectedWorker));
99 | }
100 |
101 | selectedWorker.thread.sendMessage(BackgroundThreadTaskExecutorMessage.Execute(runnable));
102 | mutex.release();
103 | }
104 |
105 | public function shutdown() : Void {
106 | for (worker in workerPool) {
107 | if (worker.thread != null) {
108 | worker.thread.sendMessage(BackgroundThreadTaskExecutorMessage.Shutdown);
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/demos/02-threads-openfl/source/org/sample/App.hx:
--------------------------------------------------------------------------------
1 | package org.sample;
2 |
3 | import hxbolts.Nothing;
4 | import hxbolts.Task;
5 | import hxbolts.executors.Executors;
6 | import motion.Actuate;
7 | import motion.easing.Linear;
8 | import openfl.Assets;
9 | import openfl.display.Bitmap;
10 | import openfl.display.FPS;
11 | import openfl.display.PixelSnapping;
12 | import openfl.display.Sprite;
13 | import openfl.text.TextField;
14 | import openfl.text.TextFormat;
15 | import openfl.text.TextFormatAlign;
16 |
17 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
18 | import sys.thread.Mutex;
19 | import sys.thread.Thread;
20 | #elseif cpp
21 | import cpp.vm.Thread;
22 | #elseif neko
23 | import neko.vm.Thread;
24 | #elseif java
25 | import java.vm.Thread;
26 | #end
27 |
28 | class App extends Sprite {
29 | private var currentPrimeTextField : TextField;
30 | private var primesFoundTextField : TextField;
31 |
32 | private var currentPrime : Int = 1;
33 | private var primesFound : Int = 1;
34 |
35 | #if (cpp || neko || java)
36 | private var uiThread : Thread;
37 | #end
38 |
39 | public function new() {
40 | super();
41 |
42 | #if (cpp || neko || java)
43 | uiThread = Thread.current();
44 | #end
45 |
46 | addChild(new FPS(10, 10, 0xff0000));
47 |
48 | var logoSprite = new Sprite();
49 | logoSprite.x = 400;
50 | logoSprite.y = 300;
51 | addChild(logoSprite);
52 |
53 | var logoBitmap = new Bitmap(Assets.getBitmapData("assets/Logo.png"), PixelSnapping.AUTO, true);
54 | logoBitmap.x = -128;
55 | logoBitmap.y = -128;
56 | logoSprite.addChild(logoBitmap);
57 |
58 | Actuate.tween(logoSprite, 3.0, { rotation: 359 }).ease(Linear.easeNone).repeat();
59 |
60 | addTextField(50, "RUNNING ANIMATION ON THE UI THREAD,");
61 | addTextField(50 + 30, "WHILE COMPUTING PRIMES AND SLEEPING");
62 |
63 | #if no_background_thread
64 | addTextField(50 + 30 * 2, "IN THE SAME UI THREAD.");
65 | #elseif (cpp || neko || java)
66 | addTextField(50 + 30 * 2, "IN THE BACKGROUND THREAD.");
67 | #else
68 | addTextField(50 + 30 * 2, "IN THE FAKE BACKGROUND THREAD.");
69 | #end
70 |
71 | currentPrimeTextField = addTextField(450);
72 | primesFoundTextField = addTextField(450 + 30);
73 |
74 | updateTextFields();
75 |
76 | trace("Let's computation begins");
77 | computeNextPrime();
78 | }
79 |
80 | private function addTextField(y : Float, ?text : String) : TextField {
81 | var textField = new TextField();
82 | textField.x = 100;
83 | textField.y = y;
84 | textField.width = 600;
85 | textField.height = 30;
86 | textField.embedFonts = true;
87 | textField.selectable = false;
88 |
89 | var textFormat = new TextFormat();
90 | textFormat.size = 24;
91 | textFormat.color = 0x303030;
92 | textFormat.font = Assets.getFont("assets/Intro.ttf").fontName;
93 | textFormat.align = TextFormatAlign.CENTER;
94 |
95 | textField.defaultTextFormat = textFormat;
96 |
97 | if (text != null) {
98 | textField.text = text;
99 | }
100 |
101 | addChild(textField);
102 | return textField;
103 | }
104 |
105 | private function updateTextFields() : Void {
106 | currentPrimeTextField.text = 'CURRENT PRIME: ${currentPrime}';
107 | primesFoundTextField.text = 'PRIMES FOUND: ${primesFound}';
108 | }
109 |
110 | private function computeNextPrime() : Void {
111 | #if (cpp || neko || java)
112 | if (!areThreadsEquals(Thread.current(), uiThread)) {
113 | trace("ERROR: Non-UI thread at start of computeNextPrime()");
114 | }
115 | #end
116 |
117 | Task.call(function() : Int {
118 | #if (cpp || neko || java)
119 | if (!areThreadsEquals(Thread.current(), uiThread)) {
120 | trace("ERROR: Non-UI in first task");
121 | }
122 | #end
123 |
124 | return currentPrime;
125 | }).continueWith(function(task : Task) : Int {
126 | #if (cpp || neko || java)
127 | #if no_background_thread
128 | if (!areThreadsEquals(Thread.current(), uiThread)) {
129 | trace("ERROR: Non-UI thread in computation function");
130 | }
131 | #else
132 | if (areThreadsEquals(Thread.current(), uiThread)) {
133 | trace("ERROR: UI thread in computation function");
134 | }
135 | #end
136 | #end
137 |
138 | var number = task.result;
139 |
140 | // Super UNoptimized alghoritm to show that computation
141 | // actually performed in the background thread
142 |
143 | while (true) {
144 | number++;
145 | var isPrime = true;
146 |
147 | for (i in 2 ... number) {
148 | if (number % i == 0) {
149 | isPrime = false;
150 | break;
151 | }
152 | }
153 |
154 | if (isPrime) {
155 | break;
156 | }
157 |
158 | #if (cpp || neko || java)
159 | Sys.sleep(0.1); // sleep for teh slowness
160 | #else
161 | var sum : Int = 0;
162 |
163 | for (i in 0 ... 100000) {
164 | sum += i;
165 | }
166 | #end
167 | }
168 |
169 | return number;
170 | } #if !no_background_thread , Executors.BACKGROUND_EXECUTOR #end).continueWith(function(task : Task) : Nothing {
171 | #if (cpp || neko || java)
172 | if (!areThreadsEquals(Thread.current(), uiThread)) {
173 | trace("ERROR: Non-UI thread at continueWith()");
174 | }
175 | #end
176 |
177 | currentPrime = task.result;
178 | primesFound++;
179 |
180 | updateTextFields();
181 | Executors.UI_EXECUTOR.execute(computeNextPrime);
182 |
183 | return null;
184 | }, Executors.UI_EXECUTOR);
185 | }
186 |
187 | #if (cpp || neko || java)
188 | private static inline function areThreadsEquals(t1 : Thread, t2 : Thread) : Bool {
189 | #if (cpp && haxe_ver >= "3.3" && have_ver < "4.0.0")
190 | return (t1.handle == t2.handle);
191 | #else
192 | return (t1 == t2);
193 | #end
194 | }
195 | #end
196 | }
197 |
--------------------------------------------------------------------------------
/tests/test/TaskExecutorsTest.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015, Viachaslau Tratsiak.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | */
9 | package ;
10 |
11 | import haxe.Timer;
12 | import hxbolts.Nothing;
13 | import hxbolts.Task;
14 | import massive.munit.Assert;
15 | import massive.munit.async.AsyncFactory;
16 | import massive.munit.util.Timer;
17 |
18 | #if (cpp || neko || java)
19 | import hxbolts.executors.BackgroundThreadTaskExecutor;
20 | import hxbolts.executors.CurrentThreadTaskExecutor;
21 | #end
22 |
23 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
24 | import sys.thread.Mutex;
25 | import sys.thread.Thread;
26 | #elseif cpp
27 | import cpp.vm.Mutex;
28 | import cpp.vm.Thread;
29 | #elseif neko
30 | import neko.vm.Mutex;
31 | import neko.vm.Thread;
32 | #elseif java
33 | import java.vm.Mutex;
34 | import java.vm.Thread;
35 | #end
36 |
37 | class TaskExecutorsTest {
38 | public function new() {
39 | }
40 |
41 | #if (cpp || neko || java)
42 |
43 | @AsyncTest
44 | public function testCurrentThreadTaskExecutor(factory : AsyncFactory) : Void {
45 | var mutex = new Mutex();
46 | var initialThread = Thread.current();
47 | var currentThreadTaskExecutor = new CurrentThreadTaskExecutor();
48 |
49 | var immediateExecuted : Bool = false;
50 | var stopLooper : Bool = false;
51 | var task1Executed : Bool = false;
52 | var task1ThreadOk : Bool = false;
53 | var task2ThreadOk : Bool = false;
54 |
55 | var handler : Dynamic = factory.createHandler(this, function() : Void {
56 | Assert.isTrue(immediateExecuted);
57 | Assert.isTrue(task1Executed);
58 | Assert.isTrue(task1ThreadOk);
59 | Assert.isTrue(task2ThreadOk);
60 | }, 5000);
61 |
62 | Task.call(function() : Nothing {
63 | Sys.sleep(0.1);
64 |
65 | mutex.acquire();
66 | task1Executed = true;
67 | task1ThreadOk = areThreadsEquals(Thread.current(), initialThread);
68 | mutex.release();
69 |
70 | return null;
71 | }, currentThreadTaskExecutor).continueWith(function(t : Task) : Nothing {
72 | task2ThreadOk = areThreadsEquals(Thread.current(), initialThread);
73 |
74 | mutex.acquire();
75 | stopLooper = true;
76 | mutex.release();
77 |
78 | handler();
79 | return null;
80 | });
81 |
82 | mutex.acquire();
83 |
84 | if (!task1Executed) {
85 | immediateExecuted = true;
86 | }
87 |
88 | mutex.release();
89 | var st = Timer.stamp();
90 |
91 | while ((Timer.stamp() - st) < 5000) {
92 | currentThreadTaskExecutor.tick();
93 |
94 | mutex.acquire();
95 | var shouldStopLooper = stopLooper;
96 | mutex.release();
97 |
98 | if (shouldStopLooper) {
99 | break;
100 | }
101 |
102 | Sys.sleep(0.1);
103 | }
104 | }
105 |
106 | @AsyncTest
107 | public function testBackgroundThreadTaskExecutor(factory : AsyncFactory) : Void {
108 | var mutex = new Mutex();
109 | var initialThread = Thread.current();
110 | var backgroundThreadTaskExecutor = new BackgroundThreadTaskExecutor(1);
111 |
112 | var immediateExecuted : Bool = false;
113 | var task1Executed : Bool = false;
114 | var task1ThreadOk : Bool = false;
115 | var task2ThreadOk : Bool = false;
116 |
117 | var handler : Dynamic = factory.createHandler(this, function() : Void {
118 | Assert.isTrue(immediateExecuted);
119 | Assert.isTrue(task1Executed);
120 | Assert.isTrue(task1ThreadOk);
121 | Assert.isTrue(task2ThreadOk);
122 |
123 | backgroundThreadTaskExecutor.shutdown();
124 | }, 5000);
125 |
126 | Task.call(function() : Nothing {
127 | Sys.sleep(0.1);
128 |
129 | mutex.acquire();
130 | task1Executed = true;
131 | task1ThreadOk = !areThreadsEquals(Thread.current(), initialThread);
132 | mutex.release();
133 |
134 | return null;
135 | }, backgroundThreadTaskExecutor).continueWith(function(t : Task) : Nothing {
136 | task2ThreadOk = !areThreadsEquals(Thread.current(), initialThread);
137 | handler();
138 | return null;
139 | });
140 |
141 | mutex.acquire();
142 |
143 | if (!task1Executed) {
144 | immediateExecuted = true;
145 | }
146 |
147 | mutex.release();
148 | }
149 |
150 | @AsyncTest
151 | public function testSwitchingBetweenExecutors(factory : AsyncFactory) : Void {
152 | var mutex = new Mutex();
153 | var initialThread = Thread.current();
154 | var currentThreadTaskExecutor = new CurrentThreadTaskExecutor();
155 | var backgroundThreadTaskExecutor = new BackgroundThreadTaskExecutor(1);
156 |
157 | var immediateExecuted : Bool = false;
158 | var stopLooper : Bool = false;
159 | var task1Executed : Bool = false;
160 | var task1ThreadOk : Bool = false;
161 | var task2ThreadOk : Bool = false;
162 | var task3ThreadOk : Bool = false;
163 | var task4ThreadOk : Bool = false;
164 |
165 | var handler : Dynamic = factory.createHandler(this, function() : Void {
166 | Assert.isTrue(immediateExecuted);
167 | Assert.isTrue(task1Executed);
168 | Assert.isTrue(task1ThreadOk);
169 | Assert.isTrue(task2ThreadOk);
170 | Assert.isTrue(task3ThreadOk);
171 | Assert.isTrue(task4ThreadOk);
172 |
173 | backgroundThreadTaskExecutor.shutdown();
174 | }, 5000);
175 |
176 | Task.call(function() : Nothing {
177 | Sys.sleep(0.1);
178 |
179 | mutex.acquire();
180 | task1Executed = true;
181 | task1ThreadOk = !areThreadsEquals(Thread.current(), initialThread);
182 | mutex.release();
183 |
184 | return null;
185 | }, backgroundThreadTaskExecutor).continueWith(function(t : Task) : Nothing {
186 | Sys.sleep(0.1);
187 |
188 | mutex.acquire();
189 | task2ThreadOk = areThreadsEquals(Thread.current(), initialThread);
190 | mutex.release();
191 |
192 | return null;
193 | }, currentThreadTaskExecutor).continueWith(function(t : Task) : Nothing {
194 | Sys.sleep(0.1);
195 |
196 | mutex.acquire();
197 | task3ThreadOk = !areThreadsEquals(Thread.current(), initialThread);
198 | mutex.release();
199 |
200 | return null;
201 | }, backgroundThreadTaskExecutor).continueWith(function(t : Task) : Nothing {
202 | Sys.sleep(0.1);
203 |
204 | mutex.acquire();
205 | task4ThreadOk = areThreadsEquals(Thread.current(), initialThread);
206 | stopLooper = true;
207 | mutex.release();
208 |
209 | handler();
210 | return null;
211 | }, currentThreadTaskExecutor);
212 |
213 | mutex.acquire();
214 |
215 | if (!task1Executed) {
216 | immediateExecuted = true;
217 | }
218 |
219 | mutex.release();
220 | var st = Timer.stamp();
221 |
222 | while ((Timer.stamp() - st) < 5000) {
223 | currentThreadTaskExecutor.tick();
224 |
225 | mutex.acquire();
226 | var shouldStopLooper = stopLooper;
227 | mutex.release();
228 |
229 | if (shouldStopLooper) {
230 | break;
231 | }
232 |
233 | Sys.sleep(0.1);
234 | }
235 | }
236 |
237 | private static inline function areThreadsEquals(t1 : Thread, t2 : Thread) : Bool {
238 | #if (cpp && haxe_ver >= "3.3" && have_ver < "4.0.0")
239 | return (t1.handle == t2.handle);
240 | #else
241 | return (t1 == t2);
242 | #end
243 | }
244 |
245 | #end
246 | }
247 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](LICENSE)
2 | [](http://www.haxe.org)
3 |
4 | # hxbolts
5 |
6 | hxbolts is a port of a "tasks" component from java library named Bolts.
7 | A task is kind of like a JavaScript Promise, but with different API.
8 |
9 | hxbolts is not a binding to the java library, but pure-haxe cross-platform port.
10 |
11 | Important note: original java library is about keeping long-running operations out of the UI thread,
12 | but current version of hxbolts is more about transforming async callback hell into nice looking code.
13 |
14 | ## Installation
15 |
16 | Stable release:
17 |
18 | ```
19 | haxelib install hxbolts
20 | ```
21 |
22 | Development version:
23 |
24 | ```
25 | haxelib git hxbolts https://github.com/restorer/hxbolts.git
26 | ```
27 |
28 | # Tasks
29 |
30 | To use all power of hxbolts, at first we need to *boltify* some existing function with callbacks.
31 | For example, let we have a function:
32 |
33 | ```haxe
34 | function doSomethingAsync(
35 | param : String,
36 | onSuccessCallback : Int -> Void,
37 | onFailureCallback : String -> Void,
38 | onCancelledCallback : Void -> Void
39 | ) : Void {
40 | ...
41 | }
42 | ```
43 |
44 | To *boltify* it create a `TaskCompletionSource`.
45 | This object will let you create a new Task, and control whether it gets marked as finished or cancelled.
46 | After you create a `Task`, you'll need to call `setResult`, `setError`, or `setCancelled` to trigger its continuations.
47 |
48 | ```haxe
49 | function doSomethingAsyncTask(param : String) : Task {
50 | var tcs = new TaskCompletionSource();
51 |
52 | doSomethingAsync(param, function(result : Int) : Void {
53 | tcs.setResult(result);
54 | }, function(failureReason : String) : Void {
55 | tcs.setError(failureReason);
56 | }, function() : Void {
57 | tcs.setCancelled();
58 | });
59 |
60 | return tcs.task;
61 | }
62 | ```
63 |
64 | That's all :smiley: Now you can chain tasks together and do all async stuff in easy manner.
65 |
66 | Another example:
67 |
68 | ```haxe
69 | function loadTextFromUrlAsync(url : String) : Task {
70 | var tcs = new TaskCompletionSource();
71 | var urlLoader = new URLLoader();
72 |
73 | var onLoaderComplete = function(_) : Void {
74 | tcs.setResult(Std.string(urlLoader.data));
75 | };
76 |
77 | var onLoaderError = function(e : Event) : Void {
78 | tcs.setError(e);
79 | };
80 |
81 | urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
82 | urlLoader.addEventListener(Event.COMPLETE, onLoaderComplete);
83 | urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoaderError);
84 | urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoaderError);
85 |
86 | try {
87 | urlLoader.load(new URLRequest(url));
88 | } catch (e : Dynamic) {
89 | tcs.setError(e);
90 | }
91 |
92 | return tcs.task;
93 | }
94 | ```
95 |
96 | ## The `continueWith` Method
97 |
98 | Every `Task` has a method named `continueWith` which takes a continuation function, which called when the task is complete.
99 | You can then inspect the task to check if it was successful and to get its result.
100 |
101 | ```haxe
102 | loadTextFromUrlAsync("http://domain.tld").continueWith(function(task : Task) : Nothing {
103 | if (task.isCancelled) {
104 | // the load was cancelled.
105 | // NB. loadTextFromUrlAsync() mentioned earlier is used just for illustration,
106 | // actually it doesn't support cancelling.
107 | } else if (task.isFaulted) {
108 | // the load failed.
109 | var error : Dynamic = task.error;
110 | } else {
111 | // the text was loaded successfully.
112 | trace(task.result);
113 | }
114 |
115 | return null;
116 | });
117 | ```
118 |
119 | Tasks are strongly-typed using generics, so getting the syntax right can be a little tricky at first.
120 | Let's look closer at the types involved with an example.
121 |
122 | ```haxe
123 | function getStringAsync() : Task {
124 | // Let's suppose getIntAsync() returns a Task.
125 | return getIntAsync().continueWith(function(task : Task) : String {
126 | // This Continuation is a function which takes an Integer as input,
127 | // and provides a String as output. It must take an Integer because
128 | // that's what was returned from the previous Task.
129 | // The Task getIntAsync() returned is passed to this function for convenience.
130 | var number : Int = task.result;
131 | return 'The number = ${number}';
132 | });
133 | }
134 | ```
135 |
136 | In many cases, you only want to do more work if the previous task was successful, and propagate any errors or cancellations to be dealt with later.
137 | To do this, use the `onSuccess` method instead of `continueWith`.
138 |
139 | ```haxe
140 | loadTextFromUrlAsync("http://domain.tld").onSuccess(function(task : Task) : Nothing {
141 | // the text was loaded successfully.
142 | trace(task.result);
143 | return null;
144 | });
145 | ```
146 |
147 | ## Chaining Tasks Together
148 |
149 | Tasks are a little bit magical, in that they let you chain them without nesting.
150 | If you use `continueWithTask` instead of `continueWith`, then you can return a new task.
151 | The task returned by `continueWithTask` will not be considered finished until the new task returned from within `continueWithTask` is.
152 | This lets you perform multiple actions without incurring the pyramid code you would get with callbacks.
153 | Likewise, `onSuccessTask` is a version of `onSuccess` that returns a new task.
154 | So, use `continueWith`/`onSuccess` to do more synchronous work, or `continueWithTask`/`onSuccessTask` to do more asynchronous work.
155 |
156 | ```haxe
157 | loadTextFromUrlAsync("http://domain.tld").onSuccessTask(function(task : Task) : Task> {
158 | return storeResultOnServerAndReturnResultCodeListAsync(task.result);
159 | }).onSuccessTask(function(task : Task>) : Task {
160 | return loadTextFromUrlAsync("http://anotherdomain.tld/index.php?ret=" + task.result.join("-"));
161 | }).onSuccessTask(function(task : Task) : Task {
162 | return storeAnotherResultOnServerAndReturnCustomResultObjectAsync(task.result);
163 | }).onSuccess(function(task : Task) : Nothing {
164 | // Everything is done!
165 | return null;
166 | });
167 | ```
168 |
169 | ## Error Handling
170 |
171 | By carefully choosing whether to call `continueWith` or `onSuccess`, you can control how errors are propagated in your application.
172 | Using `continueWith` lets you handle errors by transforming them or dealing with them.
173 | You can think of failed tasks kind of like throwing an exception.
174 | In fact, if you throw an exception inside a continuation, the resulting task will be faulted with that exception.
175 |
176 | ```haxe
177 | loadTextFromUrlAsync("http://domain.tld").onSuccessTask(function(task : Task) : Task> {
178 | // Force this callback to fail.
179 | throw "There was an error.";
180 | }).onSuccessTask(function(task : Task>) : Task {
181 | // Now this continuation will be skipped.
182 | return loadTextFromUrlAsync("http://anotherdomain.tld/index.php?ret=" + task.result.join("-"));
183 | }).continueWithTask(function(task : Task) : Task {
184 | if (task.isFaulted()) {
185 | // This error handler WILL be called.
186 | // The error will be "There was an error."
187 | // Let's handle the error by returning a new value.
188 | // The task will be completed with null as its value.
189 | return null;
190 | }
191 |
192 | // This will also be skipped.
193 | return storeAnotherResultOnServerAndReturnCustomResultObjectAsync(task.result);
194 | }).onSuccess(function(task : Task) : Nothing {
195 | // Everything is done! This gets called.
196 | // The task's result is null.
197 | return null;
198 | });
199 | ```
200 |
201 | It's often convenient to have a long chain of success callbacks with only one error handler at the end.
202 |
203 | ## Creating Tasks
204 |
205 | You already know that tasks can be created using `TaskCompletionSource`.
206 | But if you know the result of a task at the time it is created, there are some convenience methods you can use.
207 |
208 | ```haxe
209 | var successful : Task = Task.forResult("The good result.");
210 | ```
211 |
212 | ```haxe
213 | var failed : Task = Task.forError("An error message.");
214 | ```
215 |
216 | > There is also `call` function that help you create tasks from straight blocks of code.
217 | > `call` tries to execute its block immediately or at specified executor.
218 | > However in current version of hxbolts it is not really usable due to missing of good background executors.
219 |
220 | ## Tasks in Series
221 |
222 | Tasks are convenient when you want to do a series of tasks in a row, each one waiting for the previous to finish.
223 | For example, imagine you want to delete all of the comments on your blog.
224 |
225 | ```haxe
226 | findCommentsAsync({ post: 123 }).continueWithTask(function(resultTask : Task>) : Task {
227 | // Create a trivial completed task as a base case.
228 | var task : Task = Task.forResult(null);
229 |
230 | for (commentInfo in resultTask.result) {
231 | // For each item, extend the task with a function to delete the item.
232 | task = task.continueWithTask(function(_) : Task {
233 | // Return a task that will be marked as completed when the delete is finished.
234 | return deleteCommentAsync(commentInfo);
235 | });
236 | }
237 |
238 | return task;
239 | }).continueWith(function(task : Task) : Nothing {
240 | if (task.isSuccessed) {
241 | // Every comment was deleted.
242 | }
243 |
244 | return null;
245 | });
246 | ```
247 |
248 | ## Tasks in Parallel
249 |
250 | You can also perform several tasks in parallel, using the `whenAll` method.
251 | You can start multiple operations at once, and use `Task.whenAll` to create a new task that will be marked as completed when all of its input tasks are completed.
252 | The new task will be successful only if all of the passed-in tasks succeed.
253 | Performing operations in parallel will be faster than doing them serially, but may consume more system resources and bandwidth.
254 |
255 | ```haxe
256 | findCommentsAsync({ post: 123 }).continueWithTask(function(resultTask : Task>) : Task {
257 | // Collect one task for each delete into an array.
258 | var tasks = new Array>();
259 |
260 | for (commentInfo in resultTask.result) {
261 | // Start this delete immediately and add its task to the list.
262 | tasks.push(deleteCommentAsync(commentInfo));
263 | }
264 |
265 | return Task.whenAll(tasks);
266 | }).continueWith(function(task : Task) : Nothing {
267 | if (task.isSuccessed) {
268 | // Every comment was deleted.
269 | }
270 |
271 | return null;
272 | });
273 | ```
274 |
275 | ## enum Nothing
276 |
277 | Prior to Haxe 3.3 it was possible to use `Void` as return value (in reality, depending on target, it can be `null` or `undefined` or something else). **hxbolts** used this nice hack to have more clean code.
278 |
279 | Starting with Haxe 3.3 it is not possible to do this anymore - https://github.com/HaxeFoundation/haxe/issues/5519 (I agree with this decision). But we need some type for void-values:
280 |
281 | ```haxe
282 | enum Nothing {
283 | nothing;
284 | }
285 | ```
286 |
287 | You can find void-types like that in other haxelibs (for example `Nil` in **thx.core**), but to reduce dependency on other libs **hxbolts** has it own void-type.
288 |
289 | This type can have only 2 values: `Nothing.nothing` and `null`. **hxbolts** ignore these values, so you can use anything you want. Internally `null` is used (just like original java library).
290 |
291 | P.S. Nice to have void-type in standard Haxe library.
292 |
293 | ## Product support
294 |
295 | Product still is in development (but not active).
296 |
297 | | Feature | Support status |
298 | |---|---|
299 | | New features | Yes |
300 | | Non-critical bugfixes | Yes |
301 | | Critical bugfixes | Yes |
302 | | Pull requests | Accepted (after review) |
303 | | Issues | Monitored |
304 | | Estimated end-of-life | Up to 2019 |
305 |
306 | ## Roadmap for future
307 |
308 | - [ ] Support for `CancellationToken`
309 | - [ ] `@async` / `@await` on top of this library
310 |
--------------------------------------------------------------------------------
/demos/03-lime/source/org/sample/App.hx:
--------------------------------------------------------------------------------
1 | package org.sample;
2 |
3 | import haxe.Timer;
4 | import hxbolts.Nothing;
5 | import hxbolts.Task;
6 | import hxbolts.executors.Executors;
7 | import lime.app.Application;
8 | import lime.graphics.Image;
9 |
10 | #if (lime >= "7.0")
11 | import lime.utils.Assets;
12 | import lime.graphics.RenderContext;
13 | import lime.graphics.RenderContextType;
14 | #else
15 | import lime.Assets;
16 | import lime.graphics.Renderer;
17 | #end
18 |
19 | #if flash
20 | import flash.display.Bitmap;
21 | import flash.display.PixelSnapping;
22 | import flash.display.Sprite;
23 | #else
24 | import lime.graphics.opengl.GLBuffer;
25 | import lime.graphics.opengl.GLProgram;
26 | import lime.graphics.opengl.GLTexture;
27 | import lime.graphics.opengl.GLUniformLocation;
28 | import lime.math.Matrix4;
29 | import lime.math.Vector4;
30 | import lime.utils.Float32Array;
31 |
32 | #if (lime < "7.0")
33 | import lime.utils.GLUtils;
34 | #end
35 | #end
36 |
37 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
38 | import sys.thread.Mutex;
39 | import sys.thread.Thread;
40 | #elseif cpp
41 | import cpp.vm.Thread;
42 | #elseif neko
43 | import neko.vm.Thread;
44 | #elseif java
45 | import java.vm.Thread;
46 | #end
47 |
48 | class App extends Application {
49 | private var startTime : Float = -1.0;
50 | private var image : Image = null;
51 |
52 | #if flash
53 | private var logoSprite : Sprite;
54 | #else
55 | private var program : GLProgram;
56 | private var vertexAttributeLocation : Int;
57 | private var textureAttributeLocation : Int;
58 | private var matrixUniformLocation : GLUniformLocation;
59 | private var imageUniformLocation : GLUniformLocation;
60 | private var buffer : GLBuffer;
61 | private var texture : GLTexture;
62 | #end
63 |
64 | private var currentPrime : Int = 1;
65 | private var primesFound : Int = 1;
66 |
67 | #if (cpp || neko || java)
68 | private var uiThread : Thread;
69 | #end
70 |
71 | public function new() {
72 | super();
73 |
74 | #if (cpp || neko || java)
75 | uiThread = Thread.current();
76 | #end
77 |
78 | trace("RUNNING ANIMATION ON THE UI THREAD,");
79 | trace("WHILE COMPUTING PRIMES AND SLEEPING");
80 |
81 | #if no_background_thread
82 | trace("IN THE SAME UI THREAD.");
83 | #elseif (cpp || neko || java)
84 | trace("IN THE BACKGROUND THREAD.");
85 | #else
86 | trace("IN THE FAKE BACKGROUND THREAD.");
87 | #end
88 |
89 | updateTextFields();
90 |
91 | trace("Let's computation begins");
92 | computeNextPrime();
93 | }
94 |
95 | public override function render(#if (lime >= "7.0") context : RenderContext #else renderer : Renderer #end) : Void {
96 | if (image == null && preloader.complete) {
97 | image = Assets.getImage("assets/logo.png");
98 | startTime = Timer.stamp();
99 |
100 | switch (#if (lime >= "7.0") context.type #else renderer.context #end) {
101 | #if flash
102 | #if (lime >= "7.0")
103 | case FLASH: var sprite = context.flash;
104 | #else
105 | case FLASH(sprite):
106 | #end
107 | logoSprite = new Sprite();
108 | sprite.addChild(logoSprite);
109 |
110 | var logoBitmap = new Bitmap(cast image.buffer.src, PixelSnapping.AUTO, true);
111 | logoBitmap.x = - image.width / 2.0;
112 | logoBitmap.y = - image.height / 2.0;
113 | logoSprite.addChild(logoBitmap);
114 | #else
115 | #if (lime >= "7.0")
116 | case OPENGL, OPENGLES, WEBGL: var gl = context.webgl;
117 | #else
118 | case OPENGL(gl):
119 | #end
120 | var vertexShaderSource = "
121 | varying vec2 vTexCoord;
122 | attribute vec4 aPosition;
123 | attribute vec2 aTexCoord;
124 | uniform mat4 uMatrix;
125 |
126 | void main(void) {
127 | vTexCoord = aTexCoord;
128 | gl_Position = uMatrix * aPosition;
129 | }
130 | ";
131 |
132 | var fragmentShaderSource = #if !desktop "precision mediump float;" + #end "
133 | varying vec2 vTexCoord;
134 | uniform sampler2D uImage0;
135 |
136 | void main(void) {
137 | gl_FragColor = texture2D(uImage0, vTexCoord);
138 | }
139 | ";
140 |
141 | #if (lime >= "7.0")
142 | program = GLProgram.fromSources(gl, vertexShaderSource, fragmentShaderSource);
143 | #else
144 | program = GLUtils.createProgram(vertexShaderSource, fragmentShaderSource);
145 | #end
146 |
147 | gl.useProgram(program);
148 |
149 | vertexAttributeLocation = gl.getAttribLocation(program, "aPosition");
150 | textureAttributeLocation = gl.getAttribLocation(program, "aTexCoord");
151 | matrixUniformLocation = gl.getUniformLocation(program, "uMatrix");
152 | imageUniformLocation = gl.getUniformLocation(program, "uImage0");
153 |
154 | gl.enableVertexAttribArray(vertexAttributeLocation);
155 | gl.enableVertexAttribArray(textureAttributeLocation);
156 | gl.uniform1i(imageUniformLocation, 0);
157 |
158 | var data = [
159 | image.width / 2.0, image.height / 2.0, 0.0, 1.0, 1.0,
160 | - image.width / 2.0, image.height / 2.0, 0.0, 0.0, 1.0,
161 | image.width / 2.0, - image.height / 2.0, 0.0, 1.0, 0.0,
162 | - image.width / 2.0, - image.height / 2.0, 0.0, 0.0, 0.0,
163 | ];
164 |
165 | buffer = gl.createBuffer();
166 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
167 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
168 | gl.bindBuffer(gl.ARRAY_BUFFER, null);
169 |
170 | texture = gl.createTexture();
171 | gl.bindTexture(gl.TEXTURE_2D, texture);
172 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
173 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
174 |
175 | #if js
176 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image.src);
177 | #else
178 | gl.texImage2D(
179 | gl.TEXTURE_2D,
180 | 0,
181 | gl.RGBA,
182 | image.buffer.width,
183 | image.buffer.height,
184 | 0,
185 | gl.RGBA,
186 | gl.UNSIGNED_BYTE,
187 | image.data
188 | );
189 | #end
190 |
191 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
192 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
193 | gl.bindTexture(gl.TEXTURE_2D, null);
194 | #end
195 |
196 | default:
197 | #if (lime >= "7.0")
198 | throw 'Unsupported context: ${context.type}';
199 | #else
200 | throw 'Unsupported context: ${Type.enumConstructor(renderer.context)}';
201 | #end
202 | }
203 | }
204 |
205 | if (startTime < 0.0) {
206 | return;
207 | }
208 |
209 | var currentTime = Timer.stamp() - startTime;
210 |
211 | switch (#if (lime >= "7.0") context.type #else renderer.context #end) {
212 | #if flash
213 | #if (lime >= "7.0")
214 | case FLASH: var sprite = context.flash;
215 | #else
216 | case FLASH(sprite):
217 | #end
218 | logoSprite.x = sprite.stage.stageWidth / 2.0;
219 | logoSprite.y = sprite.stage.stageHeight / 2.0;
220 | logoSprite.rotation = currentTime / Math.PI * 360.0;
221 |
222 | #else
223 | #if (lime >= "7.0")
224 | case OPENGL, OPENGLES, WEBGL: var gl = context.webgl;
225 | #else
226 | case OPENGL(gl):
227 | #end
228 | gl.viewport(0, 0, window.width, window.height);
229 |
230 | gl.clearColor(1.0, 1.0, 1.0, 1.0);
231 | gl.clear(gl.COLOR_BUFFER_BIT);
232 | gl.disable(gl.CULL_FACE);
233 |
234 | #if (lime >= "7.0")
235 | var matrix = new Matrix4();
236 | matrix.createOrtho(0.0, window.width, window.height, 0.0, 0.0, 1000.0);
237 | #else
238 | var matrix = Matrix4.createOrtho(0.0, window.width, window.height, 0.0, 0.0, 1000.0);
239 | #end
240 |
241 | matrix.prependTranslation(window.width / 2.0, window.height / 2.0, 0.0);
242 | matrix.prependRotation(currentTime / Math.PI * 360.0, Vector4.Z_AXIS);
243 |
244 | gl.useProgram(program);
245 | gl.uniformMatrix4fv(matrixUniformLocation, false, matrix);
246 |
247 | gl.activeTexture(gl.TEXTURE0);
248 | gl.bindTexture(gl.TEXTURE_2D, texture);
249 |
250 | #if desktop
251 | gl.enable(gl.TEXTURE_2D);
252 | #end
253 |
254 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
255 | gl.enable(gl.BLEND);
256 |
257 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
258 | gl.vertexAttribPointer(vertexAttributeLocation, 3, gl.FLOAT, false, 5 * Float32Array.BYTES_PER_ELEMENT, 0);
259 |
260 | gl.vertexAttribPointer(
261 | textureAttributeLocation,
262 | 2,
263 | gl.FLOAT,
264 | false,
265 | 5 * Float32Array.BYTES_PER_ELEMENT,
266 | 3 * Float32Array.BYTES_PER_ELEMENT
267 | );
268 |
269 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
270 | #end
271 |
272 | default:
273 | #if (lime >= "7.0")
274 | throw 'Unsupported context: ${context.type}';
275 | #else
276 | throw 'Unsupported context: ${Type.enumConstructor(renderer.context)}';
277 | #end
278 | }
279 | }
280 |
281 | private function updateTextFields() : Void {
282 | trace('CURRENT PRIME: ${currentPrime} / PRIMES FOUND: ${primesFound}');
283 | }
284 |
285 | private function computeNextPrime() : Void {
286 | #if (cpp || neko || java)
287 | if (!areThreadsEquals(Thread.current(), uiThread)) {
288 | trace("ERROR: Non-UI thread at start of computeNextPrime()");
289 | }
290 | #end
291 |
292 | Task.call(function() : Int {
293 | #if (cpp || neko || java)
294 | if (!areThreadsEquals(Thread.current(), uiThread)) {
295 | trace("ERROR: Non-UI in first task");
296 | }
297 | #end
298 |
299 | return currentPrime;
300 | }).continueWith(function(task : Task) : Int {
301 | #if (cpp || neko || java)
302 | #if no_background_thread
303 | if (!areThreadsEquals(Thread.current(), uiThread)) {
304 | trace("ERROR: Non-UI thread in computation function");
305 | }
306 | #else
307 | if (areThreadsEquals(Thread.current(), uiThread)) {
308 | trace("ERROR: UI thread in computation function");
309 | }
310 | #end
311 | #end
312 |
313 | var number = task.result;
314 |
315 | // Super UNoptimized alghoritm to show that computation
316 | // actually performed in the background thread
317 |
318 | while (true) {
319 | number++;
320 | var isPrime = true;
321 |
322 | for (i in 2 ... number) {
323 | if (number % i == 0) {
324 | isPrime = false;
325 | break;
326 | }
327 | }
328 |
329 | if (isPrime) {
330 | break;
331 | }
332 |
333 | #if (cpp || neko || java)
334 | Sys.sleep(0.1); // sleep for teh slowness
335 | #else
336 | var sum : Int = 0;
337 |
338 | for (i in 0 ... 1000000) {
339 | sum += i;
340 | }
341 | #end
342 | }
343 |
344 | return number;
345 | } #if !no_background_thread , Executors.BACKGROUND_EXECUTOR #end).continueWith(function(task : Task) : Nothing {
346 | #if (cpp || neko || java)
347 | if (!areThreadsEquals(Thread.current(), uiThread)) {
348 | trace("ERROR: Non-UI thread at continueWith()");
349 | }
350 | #end
351 |
352 | currentPrime = task.result;
353 | primesFound++;
354 |
355 | updateTextFields();
356 | Executors.UI_EXECUTOR.execute(computeNextPrime);
357 |
358 | return null;
359 | }, Executors.UI_EXECUTOR);
360 | }
361 |
362 | #if (cpp || neko || java)
363 | private static inline function areThreadsEquals(t1 : Thread, t2 : Thread) : Bool {
364 | #if (cpp && haxe_ver >= "3.3" && have_ver < "4.0.0")
365 | return (t1.handle == t2.handle);
366 | #else
367 | return (t1 == t2);
368 | #end
369 | }
370 | #end
371 | }
372 |
--------------------------------------------------------------------------------
/hxbolts/Task.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Original java implementation:
3 | * Copyright (c) 2014, Facebook, Inc.
4 | * All rights reserved.
5 | *
6 | * Haxe version:
7 | * Copyright (c) 2015, Viachaslau Tratsiak.
8 | * All rights reserved.
9 | *
10 | * This source code is licensed under the BSD-style license found in the
11 | * LICENSE file in the root directory of this source tree. An additional grant
12 | * of patent rights can be found in the PATENTS file in the original project repo:
13 | * https://github.com/BoltsFramework/Bolts-Android/
14 | *
15 | */
16 | package hxbolts;
17 |
18 | import hxbolts.executors.Executors;
19 | import hxbolts.executors.TaskExecutor;
20 |
21 | #if (haxe_ver >= "4.0.0" && (cpp || neko || java))
22 | import sys.thread.Mutex;
23 | #elseif cpp
24 | import cpp.vm.Mutex;
25 | #elseif neko
26 | import neko.vm.Mutex;
27 | #elseif java
28 | import java.vm.Mutex;
29 | #end
30 |
31 | class Task {
32 | private var _isCompleted : Bool;
33 | private var _isFaulted : Bool;
34 | private var _isCancelled : Bool;
35 | private var _result : Null;
36 | private var _error : Dynamic;
37 |
38 | public var isCompleted(get, never) : Bool;
39 | public var isCancelled(get, never) : Bool;
40 | public var isFaulted(get, never) : Bool;
41 | public var isSuccessed(get, never) : Bool;
42 | public var result(get, never) : Null;
43 | public var error(get, never) : Dynamic;
44 |
45 | private var continuations : Array -> Nothing>;
46 |
47 | #if (cpp || neko || java)
48 | private var mutex : Mutex;
49 | #end
50 |
51 | private function new() {
52 | _isCompleted = false;
53 | _isFaulted = false;
54 | _isCancelled = false;
55 | _result = null;
56 | _error = null;
57 | continuations = [];
58 |
59 | #if (cpp || neko || java)
60 | mutex = new Mutex();
61 | #end
62 | }
63 |
64 | @:deprecated
65 | public inline function makeVoid() : Task {
66 | return makeNothing();
67 | }
68 |
69 | public function makeNothing() : Task {
70 | return continueWithTask(function(task : Task) : Task {
71 | if (task.isCancelled) {
72 | return cast Task.cancelled();
73 | }
74 |
75 | if (task.isFaulted) {
76 | return cast Task.forError(task.error);
77 | }
78 |
79 | return cast Task.forResult(null);
80 | });
81 | }
82 |
83 | public function continueWhile(
84 | predicate : Void -> Bool,
85 | continuation : Task -> Task,
86 | ?executor : TaskExecutor
87 | ) : Task {
88 | var predicateContinuation = new Array -> Task>();
89 |
90 | predicateContinuation.push(function(task : Task) : Task {
91 | if (predicate()) {
92 | return cast Task.forResult(null)
93 | .onSuccessTask(continuation, executor)
94 | .onSuccessTask(predicateContinuation[0], executor);
95 | }
96 |
97 | return cast Task.forResult(null);
98 | });
99 |
100 | return makeNothing().continueWithTask(predicateContinuation[0], executor);
101 | }
102 |
103 | public function continueWith(
104 | continuation : Task -> TContinuationResult,
105 | ?executor : TaskExecutor
106 | ) : Task {
107 | if (executor == null) {
108 | executor = Executors.IMMEDIATE_EXECUTOR;
109 | }
110 |
111 | var tcs = new TaskCompletionSource();
112 |
113 | #if (cpp || neko || java)
114 | mutex.acquire();
115 | var wasCompleted = _isCompleted;
116 |
117 | if (!wasCompleted) {
118 | continuations.push(function(task : Task) : Nothing {
119 | completeImmediately(tcs, continuation, task, executor);
120 | return null;
121 | });
122 | }
123 |
124 | mutex.release();
125 |
126 | if (wasCompleted) {
127 | completeImmediately(tcs, continuation, this, executor);
128 | }
129 | #else
130 | if (_isCompleted) {
131 | completeImmediately(tcs, continuation, this, executor);
132 | } else {
133 | continuations.push(function(task : Task) : Nothing {
134 | completeImmediately(tcs, continuation, task, executor);
135 | return null;
136 | });
137 | }
138 | #end
139 |
140 | return tcs.task;
141 | }
142 |
143 | public function continueWithTask(
144 | continuation : Task -> Task,
145 | ?executor : TaskExecutor
146 | ) : Task {
147 | if (executor == null) {
148 | executor = Executors.IMMEDIATE_EXECUTOR;
149 | }
150 |
151 | var tcs = new TaskCompletionSource();
152 |
153 | #if (cpp || neko || java)
154 | mutex.acquire();
155 | var wasCompleted = _isCompleted;
156 |
157 | if (!wasCompleted) {
158 | continuations.push(function(task : Task) : Nothing {
159 | completeAfterTask(tcs, continuation, task, executor);
160 | return null;
161 | });
162 | }
163 |
164 | mutex.release();
165 |
166 | if (wasCompleted) {
167 | completeAfterTask(tcs, continuation, this, executor);
168 | }
169 | #else
170 | if (_isCompleted) {
171 | completeAfterTask(tcs, continuation, this, executor);
172 | } else {
173 | continuations.push(function(task : Task) : Nothing {
174 | completeAfterTask(tcs, continuation, task, executor);
175 | return null;
176 | });
177 | }
178 | #end
179 |
180 | return tcs.task;
181 | }
182 |
183 | public function onSuccess(
184 | continuation : Task -> TContinuationResult,
185 | ?executor : TaskExecutor
186 | ) : Task {
187 | return continueWithTask(function(task : Task) : Task {
188 | if (task.isFaulted) {
189 | return cast Task.forError(task.error);
190 | } else if (task.isCancelled) {
191 | return cast Task.cancelled();
192 | } else {
193 | return task.continueWith(continuation);
194 | }
195 | }, executor);
196 | }
197 |
198 | public function onSuccessTask(
199 | continuation : Task -> Task,
200 | ?executor : TaskExecutor
201 | ) : Task {
202 | return continueWithTask(function(task : Task) : Task {
203 | if (task.isFaulted) {
204 | return cast Task.forError(task.error);
205 | } else if (task.isCancelled) {
206 | return cast Task.cancelled();
207 | } else {
208 | return task.continueWithTask(continuation);
209 | }
210 | }, executor);
211 | }
212 |
213 | // caller function must guard call to runContinuations with mutex
214 | private function runContinuations() : Void {
215 | for (continuation in continuations) {
216 | // do not catch exceptions here
217 | continuation(this);
218 | }
219 |
220 | continuations = null;
221 | }
222 |
223 | @:noCompletion
224 | private function get_isCompleted() : Bool {
225 | #if (cpp || neko || java)
226 | mutex.acquire();
227 | var ret : Bool = _isCompleted;
228 | mutex.release();
229 | return ret;
230 | #else
231 | return _isCompleted;
232 | #end
233 | }
234 |
235 | @:noCompletion
236 | private function get_isCancelled() : Bool {
237 | #if (cpp || neko || java)
238 | mutex.acquire();
239 | var ret : Bool = _isCancelled;
240 | mutex.release();
241 | return ret;
242 | #else
243 | return _isCancelled;
244 | #end
245 | }
246 |
247 | @:noCompletion
248 | private function get_isFaulted() : Bool {
249 | #if (cpp || neko || java)
250 | mutex.acquire();
251 | var ret : Bool = _isFaulted;
252 | mutex.release();
253 | return ret;
254 | #else
255 | return _isFaulted;
256 | #end
257 | }
258 |
259 | @:noCompletion
260 | private function get_isSuccessed() : Bool {
261 | #if (cpp || neko || java)
262 | mutex.acquire();
263 | var ret : Bool = (_isCompleted && !_isFaulted && !_isCancelled);
264 | mutex.release();
265 | return ret;
266 | #else
267 | return (_isCompleted && !_isFaulted && !_isCancelled);
268 | #end
269 | }
270 |
271 | @:noCompletion
272 | private function get_result() : Null {
273 | #if (cpp || neko || java)
274 | mutex.acquire();
275 | var ret : Null = _result;
276 | mutex.release();
277 | return ret;
278 | #else
279 | return _result;
280 | #end
281 | }
282 |
283 | @:noCompletion
284 | private function get_error() : Dynamic {
285 | #if (cpp || neko || java)
286 | mutex.acquire();
287 | var ret : Dynamic = _error;
288 | mutex.release();
289 | return ret;
290 | #else
291 | return _error;
292 | #end
293 | }
294 |
295 | public static function forResult(value : Null) : Task {
296 | var tcs = new TaskCompletionSource();
297 | tcs.setResult(value);
298 | return tcs.task;
299 | }
300 |
301 | public static function forError(value : Dynamic) : Task {
302 | var tcs = new TaskCompletionSource();
303 | tcs.setError(value);
304 | return tcs.task;
305 | }
306 |
307 | public static function cancelled() : Task {
308 | var tcs = new TaskCompletionSource();
309 | tcs.setCancelled();
310 | return tcs.task;
311 | }
312 |
313 | public static function call(callable : Void -> Null, ?executor : TaskExecutor) : Task {
314 | if (executor == null) {
315 | executor = Executors.IMMEDIATE_EXECUTOR;
316 | }
317 |
318 | var tcs = new TaskCompletionSource();
319 |
320 | executor.execute(function() : Void {
321 | try {
322 | tcs.setResult(callable());
323 | } catch (e : TaskCancellationException) {
324 | tcs.setCancelled();
325 | } catch (e : Dynamic) {
326 | tcs.setError(e);
327 | }
328 | });
329 |
330 | return tcs.task;
331 | }
332 |
333 | public static function whenAny(tasks : Array>) : Task> {
334 | if (tasks.length == 0) {
335 | return cast Task.forResult(null);
336 | }
337 |
338 | var firstCompleted = new TaskCompletionSource>();
339 | var isAnyTaskComplete : Bool = false;
340 |
341 | #if (cpp || neko || java)
342 | var valMutex : Mutex = new Mutex();
343 | #end
344 |
345 | for (t in tasks) {
346 | t.continueWith(function(task : Task) : Nothing {
347 | #if (cpp || neko || java)
348 | var val = false;
349 | valMutex.acquire();
350 |
351 | if (!isAnyTaskComplete) {
352 | isAnyTaskComplete = true;
353 | val = true;
354 | }
355 |
356 | valMutex.release();
357 |
358 | if (val) {
359 | firstCompleted.setResult(task);
360 | }
361 | #else
362 | if (!isAnyTaskComplete) {
363 | isAnyTaskComplete = true;
364 | firstCompleted.setResult(task);
365 | }
366 | #end
367 |
368 | return null;
369 | });
370 | }
371 |
372 | return firstCompleted.task;
373 | }
374 |
375 | public static function whenAll(tasks : Array>) : Task {
376 | if (tasks.length == 0) {
377 | return cast Task.forResult(null);
378 | }
379 |
380 | var allFinished = new TaskCompletionSource();
381 | var causes = new Array();
382 | var count = tasks.length;
383 | var isAnyCancelled = false;
384 |
385 | #if (cpp || neko || java)
386 | var valMutex : Mutex = new Mutex();
387 | #end
388 |
389 | for (t in tasks) {
390 | t.continueWith(function(task : Task) : Nothing {
391 | if (task.isFaulted) {
392 | #if (cpp || neko || java)
393 | valMutex.acquire();
394 | causes.push(task.error);
395 | valMutex.release();
396 | #else
397 | causes.push(task.error);
398 | #end
399 | }
400 |
401 | if (task.isCancelled) {
402 | #if (cpp || neko || java)
403 | valMutex.acquire();
404 | isAnyCancelled = true;
405 | valMutex.release();
406 | #else
407 | isAnyCancelled = true;
408 | #end
409 | }
410 |
411 | #if (cpp || neko || java)
412 | valMutex.acquire();
413 | count--;
414 | var val = (count == 0);
415 | valMutex.release();
416 |
417 | if (val) {
418 | if (causes.length != 0) {
419 | allFinished.setError(causes);
420 | } else {
421 | valMutex.acquire();
422 | val = isAnyCancelled;
423 | valMutex.release();
424 |
425 | if (val) {
426 | allFinished.setCancelled();
427 | } else {
428 | allFinished.setResult(null);
429 | }
430 | }
431 | }
432 | #else
433 | count--;
434 |
435 | if (count == 0) {
436 | if (causes.length != 0) {
437 | allFinished.setError(causes);
438 | } else if (isAnyCancelled) {
439 | allFinished.setCancelled();
440 | } else {
441 | allFinished.setResult(null);
442 | }
443 | }
444 | #end
445 |
446 | return null;
447 | });
448 | }
449 |
450 | return allFinished.task;
451 | }
452 |
453 | public static function whenAllResult(tasks : Array>) : Task>> {
454 | return whenAll(tasks).onSuccess(function(task : Task) : Array> {
455 | var results = new Array>();
456 |
457 | for (t in tasks) {
458 | results.push(t.result);
459 | }
460 |
461 | return results;
462 | });
463 | }
464 |
465 | private static function completeImmediately(
466 | tcs : TaskCompletionSource,
467 | continuation : Task -> TContinuationResult,
468 | task : Task,
469 | executor : TaskExecutor
470 | ) : Void {
471 | executor.execute(function() : Void {
472 | try {
473 | tcs.setResult(continuation(task));
474 | } catch (e : TaskCancellationException) {
475 | tcs.setCancelled();
476 | } catch (e : Dynamic) {
477 | tcs.setError(e);
478 | }
479 | });
480 | }
481 |
482 | private static function completeAfterTask(
483 | tcs : TaskCompletionSource,
484 | continuation : Task -> Task,
485 | task : Task,
486 | executor : TaskExecutor
487 | ) : Void {
488 | executor.execute(function() : Void {
489 | try {
490 | var resultTask = continuation(task);
491 |
492 | if (resultTask == null) {
493 | tcs.setResult(null);
494 | } else {
495 | resultTask.continueWith(function(task : Task) : Nothing {
496 | if (task.isFaulted) {
497 | tcs.setError(task.error);
498 | } else if (task.isCancelled) {
499 | tcs.setCancelled();
500 | } else {
501 | tcs.setResult(task.result);
502 | }
503 |
504 | return null;
505 | });
506 | }
507 | } catch (e : TaskCancellationException) {
508 | tcs.setCancelled();
509 | } catch (e : Dynamic) {
510 | tcs.setError(e);
511 | }
512 | });
513 | }
514 | }
515 |
--------------------------------------------------------------------------------
/tests/test/TaskTest.hx:
--------------------------------------------------------------------------------
1 | /*
2 | * Original java implementation:
3 | * Copyright (c) 2014, Facebook, Inc.
4 | * All rights reserved.
5 | *
6 | * Haxe version:
7 | * Copyright (c) 2015, Viachaslau Tratsiak.
8 | * All rights reserved.
9 | *
10 | * This source code is licensed under the BSD-style license found in the
11 | * LICENSE file in the root directory of this source tree. An additional grant
12 | * of patent rights can be found in the PATENTS file in the original project repo:
13 | * https://github.com/BoltsFramework/Bolts-Android/
14 | *
15 | */
16 | package ;
17 |
18 | import hxbolts.Nothing;
19 | import hxbolts.Task;
20 | import hxbolts.TaskCancellationException;
21 | import hxbolts.TaskCompletionSource;
22 | import massive.munit.Assert;
23 | import massive.munit.async.AsyncFactory;
24 | import util.TestException;
25 | import util.TimerExecutor;
26 |
27 | class TaskTest {
28 | public function new() {
29 | }
30 |
31 | @Test
32 | public function testPrimitives() : Void {
33 | var complete : Task = Task.forResult(5);
34 | var error : Task = Task.forError(new TestException());
35 | var cancelled : Task = Task.cancelled();
36 |
37 | Assert.isTrue(complete.isCompleted);
38 | Assert.areEqual(5, complete.result);
39 | Assert.isFalse(complete.isFaulted);
40 | Assert.isFalse(complete.isCancelled);
41 | Assert.isTrue(complete.isSuccessed);
42 |
43 | Assert.isTrue(error.isCompleted);
44 | Assert.isType(error.error, TestException);
45 | Assert.isTrue(error.isFaulted);
46 | Assert.isFalse(error.isCancelled);
47 | Assert.isFalse(error.isSuccessed);
48 |
49 | Assert.isTrue(cancelled.isCompleted);
50 | Assert.isFalse(cancelled.isFaulted);
51 | Assert.isTrue(cancelled.isCancelled);
52 | Assert.isFalse(cancelled.isSuccessed);
53 | }
54 |
55 | @Test
56 | public function testSynchronousContinuation() : Void {
57 | var complete : Task = Task.forResult(5);
58 | var error : Task = Task.forError(new TestException());
59 | var cancelled : Task = Task.cancelled();
60 |
61 | var completeHandled : Bool = false;
62 | var errorHandled : Bool = false;
63 | var cancelledHandled : Bool = false;
64 |
65 | complete.continueWith(function(task : Task) : Nothing {
66 | Assert.areSame(complete, task);
67 |
68 | Assert.isTrue(task.isCompleted);
69 | Assert.areEqual(5, task.result);
70 | Assert.isFalse(task.isFaulted);
71 | Assert.isFalse(task.isCancelled);
72 | Assert.isTrue(task.isSuccessed);
73 |
74 | completeHandled = true;
75 | return null;
76 | });
77 |
78 | error.continueWith(function(task : Task) : Nothing {
79 | Assert.areSame(error, task);
80 |
81 | Assert.isTrue(task.isCompleted);
82 | Assert.isType(task.error, TestException);
83 | Assert.isTrue(task.isFaulted);
84 | Assert.isFalse(task.isCancelled);
85 | Assert.isFalse(task.isSuccessed);
86 |
87 | errorHandled = true;
88 | return null;
89 | });
90 |
91 | cancelled.continueWith(function(task : Task) : Nothing {
92 | Assert.areSame(cancelled, task);
93 |
94 | Assert.isTrue(task.isCompleted);
95 | Assert.isFalse(task.isFaulted);
96 | Assert.isTrue(task.isCancelled);
97 | Assert.isFalse(task.isSuccessed);
98 |
99 | cancelledHandled = true;
100 | return null;
101 | });
102 |
103 | Assert.isTrue(completeHandled);
104 | Assert.isTrue(errorHandled);
105 | Assert.isTrue(cancelledHandled);
106 | }
107 |
108 | @Test
109 | public function testSynchronousChaining() : Void {
110 | var first : Task = Task.forResult(1);
111 |
112 | var second : Task = first.continueWith(function(task : Task) : Int {
113 | return 2;
114 | });
115 |
116 | var third = second.continueWithTask(function(task : Task) : Task {
117 | return Task.forResult(3);
118 | });
119 |
120 | Assert.isTrue(first.isCompleted);
121 | Assert.isTrue(second.isCompleted);
122 | Assert.isTrue(third.isCompleted);
123 |
124 | Assert.areEqual(1, first.result);
125 | Assert.areEqual(2, second.result);
126 | Assert.areEqual(3, third.result);
127 | }
128 |
129 | @Test
130 | public function testSynchronousCancellation() : Void {
131 | var first : Task = Task.forResult(1);
132 |
133 | var second : Task = first.continueWith(function(task : Task) : Int {
134 | throw new TaskCancellationException();
135 | });
136 |
137 | Assert.isTrue(first.isCompleted);
138 | Assert.isTrue(second.isCancelled);
139 | }
140 |
141 | @Test
142 | public function testSynchronousTaskCancellation() : Void {
143 | var first : Task = Task.forResult(1);
144 |
145 | var second : Task = first.continueWithTask(function(task : Task) : Task {
146 | throw new TaskCancellationException();
147 | });
148 |
149 | Assert.isTrue(first.isCompleted);
150 | Assert.isTrue(second.isCancelled);
151 | }
152 |
153 | @AsyncTest
154 | public function testBackgroundCall(factory : AsyncFactory) : Void {
155 | var timerExecutor = new TimerExecutor(10);
156 | var task : Task = null;
157 |
158 | var handler : Dynamic = factory.createHandler(this, function() : Void {
159 | Assert.areEqual(5, task.result);
160 | }, 5000);
161 |
162 | Task.call(function() : Int {
163 | return 5;
164 | }, timerExecutor).continueWith(function(t : Task) : Nothing {
165 | task = t;
166 | handler();
167 | return null;
168 | });
169 | }
170 |
171 | @AsyncTest
172 | public function testBackgroundError(factory : AsyncFactory) : Void {
173 | var timerExecutor = new TimerExecutor(10);
174 | var task : Task = null;
175 |
176 | var handler : Dynamic = factory.createHandler(this, function() : Void {
177 | Assert.isTrue(task.isFaulted);
178 | Assert.isType(task.error, TestException);
179 | }, 5000);
180 |
181 | Task.call(function() : Int {
182 | throw new TestException();
183 | }, timerExecutor).continueWith(function(t : Task) : Nothing {
184 | task = t;
185 | handler();
186 | return null;
187 | });
188 | }
189 |
190 | @AsyncTest
191 | public function testBackgroundCancellation(factory : AsyncFactory) : Void {
192 | var timerExecutor = new TimerExecutor(10);
193 | var task : Task = null;
194 |
195 | var handler : Dynamic = factory.createHandler(this, function() : Void {
196 | Assert.isTrue(task.isCancelled);
197 | }, 5000);
198 |
199 | Task.call(function() : Int {
200 | throw new TaskCancellationException();
201 | }, timerExecutor).continueWith(function(t : Task) : Nothing {
202 | task = t;
203 | handler();
204 | return null;
205 | });
206 | }
207 |
208 | @AsyncTest
209 | public function testContinueOnTimerExecutor(factory : AsyncFactory) : Void {
210 | var timerExecutor = new TimerExecutor(10);
211 | var task : Task = null;
212 |
213 | var handler : Dynamic = factory.createHandler(this, function() : Void {
214 | Assert.areEqual(3, task.result);
215 | }, 5000);
216 |
217 | Task.call(function() : Int {
218 | return 1;
219 | }, timerExecutor).continueWith(function(t : Task) : Int {
220 | return t.result + 1;
221 | }, timerExecutor).continueWithTask(function(t : Task) : Task {
222 | return Task.forResult(t.result + 1);
223 | }, timerExecutor).continueWith(function(t : Task) : Nothing {
224 | task = t;
225 | handler();
226 | return null;
227 | });
228 | }
229 |
230 | @Test
231 | public function testWhenAllNoTasks() : Void {
232 | var task : Task = Task.whenAll(new Array>());
233 |
234 | Assert.isTrue(task.isCompleted);
235 | Assert.isFalse(task.isFaulted);
236 | Assert.isFalse(task.isCancelled);
237 | Assert.isTrue(task.isSuccessed);
238 | }
239 |
240 | @AsyncTest
241 | public function testWhenAnyResultFirstSuccess(factory : AsyncFactory) : Void {
242 | var task : Task> = null;
243 | var tasks = new Array>();
244 |
245 | var firstToCompleteSuccess = Task.call(function() : Int {
246 | return 2000;
247 | }, new TimerExecutor(50));
248 |
249 | addTasksWithRandomCompletions(tasks, 5);
250 | tasks.push(firstToCompleteSuccess);
251 | addTasksWithRandomCompletions(tasks, 5);
252 |
253 | var handler : Dynamic = factory.createHandler(this, function() : Void {
254 | Assert.isTrue(task.isCompleted);
255 | Assert.isFalse(task.isFaulted);
256 | Assert.isFalse(task.isCancelled);
257 | Assert.isTrue(task.isSuccessed);
258 |
259 | Assert.areSame(firstToCompleteSuccess, task.result);
260 |
261 | Assert.isTrue(task.result.isCompleted);
262 | Assert.isFalse(task.result.isFaulted);
263 | Assert.isFalse(task.result.isCancelled);
264 | Assert.isTrue(task.result.isSuccessed);
265 |
266 | Assert.areEqual(2000, task.result.result);
267 | }, 5000);
268 |
269 | Task.whenAny(tasks).continueWith(function(t : Task>) : Nothing {
270 | task = t;
271 | handler();
272 | return null;
273 | });
274 | }
275 |
276 | @AsyncTest
277 | public function testWhenAnyFirstSuccess(factory : AsyncFactory) : Void {
278 | var task : Task> = null;
279 | var tasks = new Array>();
280 |
281 | var firstToCompleteSuccess : Task = Task.call(function() : String {
282 | return "SUCCESS";
283 | }, new TimerExecutor(50));
284 |
285 | addTasksWithRandomCompletions(tasks, 5);
286 | tasks.push(firstToCompleteSuccess);
287 | addTasksWithRandomCompletions(tasks, 5);
288 |
289 | var handler : Dynamic = factory.createHandler(this, function() : Void {
290 | Assert.isTrue(task.isCompleted);
291 | Assert.isFalse(task.isFaulted);
292 | Assert.isFalse(task.isCancelled);
293 | Assert.isTrue(task.isSuccessed);
294 |
295 | Assert.areSame(firstToCompleteSuccess, task.result);
296 |
297 | Assert.isTrue(task.result.isCompleted);
298 | Assert.isFalse(task.result.isFaulted);
299 | Assert.isFalse(task.result.isCancelled);
300 | Assert.isTrue(task.result.isSuccessed);
301 |
302 | Assert.areEqual("SUCCESS", task.result.result);
303 | }, 5000);
304 |
305 | Task.whenAny(tasks).continueWith(function(t : Task>) : Nothing {
306 | task = t;
307 | handler();
308 | return null;
309 | });
310 | }
311 |
312 | @AsyncTest
313 | public function testWhenAnyFirstError(factory : AsyncFactory) : Void {
314 | var task : Task> = null;
315 | var error = new TestException();
316 | var tasks = new Array>();
317 |
318 | var firstToCompleteError : Task = Task.call(function() : String {
319 | throw error;
320 | }, new TimerExecutor(50));
321 |
322 | addTasksWithRandomCompletions(tasks, 5);
323 | tasks.push(firstToCompleteError);
324 | addTasksWithRandomCompletions(tasks, 5);
325 |
326 | var handler : Dynamic = factory.createHandler(this, function() : Void {
327 | Assert.isTrue(task.isCompleted);
328 | Assert.isFalse(task.isFaulted);
329 | Assert.isFalse(task.isCancelled);
330 | Assert.isTrue(task.isSuccessed);
331 |
332 | Assert.areSame(firstToCompleteError, task.result);
333 |
334 | Assert.isTrue(task.result.isCompleted);
335 | Assert.isTrue(task.result.isFaulted);
336 | Assert.isFalse(task.result.isCancelled);
337 | Assert.isFalse(task.result.isSuccessed);
338 |
339 | Assert.areSame(error, task.result.error);
340 | }, 5000);
341 |
342 | Task.whenAny(tasks).continueWith(function(t : Task>) : Nothing {
343 | task = t;
344 | handler();
345 | return null;
346 | });
347 | }
348 |
349 | @AsyncTest
350 | public function testWhenAnyFirstCancelled(factory : AsyncFactory) : Void {
351 | var task : Task> = null;
352 | var tasks = new Array>();
353 |
354 | var firstToCompleteError : Task = Task.call(function() : String {
355 | throw new TaskCancellationException();
356 | }, new TimerExecutor(50));
357 |
358 | addTasksWithRandomCompletions(tasks, 5);
359 | tasks.push(firstToCompleteError);
360 | addTasksWithRandomCompletions(tasks, 5);
361 |
362 | var handler : Dynamic = factory.createHandler(this, function() : Void {
363 | Assert.isTrue(task.isCompleted);
364 | Assert.isFalse(task.isFaulted);
365 | Assert.isFalse(task.isCancelled);
366 | Assert.isTrue(task.isSuccessed);
367 |
368 | Assert.areSame(firstToCompleteError, task.result);
369 |
370 | Assert.isTrue(task.result.isCompleted);
371 | Assert.isFalse(task.result.isFaulted);
372 | Assert.isTrue(task.result.isCancelled);
373 | Assert.isFalse(task.result.isSuccessed);
374 | }, 5000);
375 |
376 | Task.whenAny(tasks).continueWith(function(t : Task>) : Nothing {
377 | task = t;
378 | handler();
379 | return null;
380 | });
381 | }
382 |
383 | @AsyncTest
384 | public function testWhenAllSuccess(factory : AsyncFactory) : Void {
385 | var task : Task = null;
386 | var tasks = new Array>();
387 |
388 | for (i in 0 ... 20) {
389 | tasks.push(Task.call(function() : Nothing {
390 | // do nothing
391 | return null;
392 | }, new TimerExecutor(randomInt(10, 50))));
393 | }
394 |
395 | var handler : Dynamic = factory.createHandler(this, function() : Void {
396 | Assert.isTrue(task.isCompleted);
397 | Assert.isFalse(task.isFaulted);
398 | Assert.isFalse(task.isCancelled);
399 | Assert.isTrue(task.isSuccessed);
400 |
401 | for (t in tasks) {
402 | Assert.isTrue(t.isCompleted);
403 | }
404 | }, 5000);
405 |
406 | Task.whenAll(tasks).continueWith(function(t : Task) : Nothing {
407 | task = t;
408 | handler();
409 | return null;
410 | });
411 | }
412 |
413 | @AsyncTest
414 | public function testWhenAllOneError(factory : AsyncFactory) : Void {
415 | var task : Task = null;
416 | var error = new TestException();
417 | var tasks = new Array>();
418 |
419 | for (i in 0 ... 20) {
420 | tasks.push(Task.call(function() : Nothing {
421 | if (i == 10) {
422 | throw error;
423 | }
424 |
425 | return null;
426 | }, new TimerExecutor(randomInt(10, 50))));
427 | }
428 |
429 | var handler : Dynamic = factory.createHandler(this, function() : Void {
430 | Assert.isTrue(task.isCompleted);
431 | Assert.isTrue(task.isFaulted);
432 | Assert.isFalse(task.isCancelled);
433 | Assert.isFalse(task.isSuccessed);
434 |
435 | Assert.isType(task.error, Array);
436 | Assert.areEqual((cast task.error:Array).length, 1);
437 | Assert.areSame((cast task.error:Array)[0], error);
438 |
439 | for (t in tasks) {
440 | Assert.isTrue(t.isCompleted);
441 | }
442 | }, 5000);
443 |
444 | Task.whenAll(tasks).continueWith(function(t : Task) : Nothing {
445 | task = t;
446 | handler();
447 | return null;
448 | });
449 | }
450 |
451 | @AsyncTest
452 | public function testWhenAllTwoErrors(factory : AsyncFactory) : Void {
453 | var task : Task = null;
454 | var error0 = new TestException();
455 | var error1 = new TestException();
456 | var tasks = new Array>();
457 |
458 | for (i in 0 ... 20) {
459 | tasks.push(Task.call(function() : Nothing {
460 | if (i == 10) {
461 | throw error0;
462 | } else if (i == 11) {
463 | throw error1;
464 | }
465 |
466 | return null;
467 | }, new TimerExecutor(10 + i * 10)));
468 | }
469 |
470 | var handler : Dynamic = factory.createHandler(this, function() : Void {
471 | Assert.isTrue(task.isCompleted);
472 | Assert.isTrue(task.isFaulted);
473 | Assert.isFalse(task.isCancelled);
474 | Assert.isFalse(task.isSuccessed);
475 |
476 | Assert.isType(task.error, Array);
477 | Assert.areEqual((cast task.error:Array).length, 2);
478 | Assert.areSame((cast task.error:Array)[0], error0);
479 | Assert.areSame((cast task.error:Array)[1], error1);
480 |
481 | for (t in tasks) {
482 | Assert.isTrue(t.isCompleted);
483 | }
484 | }, 5000);
485 |
486 | Task.whenAll(tasks).continueWith(function(t : Task) : Nothing {
487 | task = t;
488 | handler();
489 | return null;
490 | });
491 | }
492 |
493 | @AsyncTest
494 | public function testWhenAllCancel(factory : AsyncFactory) : Void {
495 | var task : Task = null;
496 | var tasks = new Array>();
497 |
498 | for (i in 0 ... 20) {
499 | var tcs = new TaskCompletionSource();
500 |
501 | Task.call(function() : Nothing {
502 | if (i == 10) {
503 | tcs.setCancelled();
504 | } else {
505 | tcs.setResult(null);
506 | }
507 |
508 | return null;
509 | }, new TimerExecutor(randomInt(10, 50)));
510 |
511 | tasks.push(tcs.task);
512 | }
513 |
514 | var handler : Dynamic = factory.createHandler(this, function() : Void {
515 | Assert.isTrue(task.isCompleted);
516 | Assert.isFalse(task.isFaulted);
517 | Assert.isTrue(task.isCancelled);
518 | Assert.isFalse(task.isSuccessed);
519 |
520 | for (t in tasks) {
521 | Assert.isTrue(t.isCompleted);
522 | }
523 | }, 5000);
524 |
525 | Task.whenAll(tasks).continueWith(function(t : Task) : Nothing {
526 | task = t;
527 | handler();
528 | return null;
529 | });
530 | }
531 |
532 | @Test
533 | public function testWhenAllResultNoTasks() : Void {
534 | var task : Task> = Task.whenAllResult(new Array>());
535 |
536 | Assert.isTrue(task.isCompleted);
537 | Assert.isFalse(task.isFaulted);
538 | Assert.isFalse(task.isCancelled);
539 | Assert.isTrue(task.isSuccessed);
540 |
541 | Assert.areEqual(task.result.length, 0);
542 | }
543 |
544 | @AsyncTest
545 | public function testWhenAllResultSuccess(factory : AsyncFactory) : Void {
546 | var task : Task> = null;
547 | var tasks = new Array>();
548 |
549 | for (i in 0 ... 20) {
550 | tasks.push(Task.call(function() : Int {
551 | return (i + 1);
552 | }, new TimerExecutor(randomInt(10, 50))));
553 | }
554 |
555 | var handler : Dynamic = factory.createHandler(this, function() : Void {
556 | Assert.isTrue(task.isCompleted);
557 | Assert.isFalse(task.isFaulted);
558 | Assert.isFalse(task.isCancelled);
559 | Assert.isTrue(task.isSuccessed);
560 |
561 | Assert.areEqual(tasks.length, task.result.length);
562 |
563 | for (i in 0 ... tasks.length) {
564 | var t = tasks[i];
565 | Assert.isTrue(t.isCompleted);
566 | Assert.areEqual(t.result, task.result[i]);
567 | }
568 | }, 5000);
569 |
570 | Task.whenAllResult(tasks).continueWith(function(t : Task>) : Nothing {
571 | task = t;
572 | handler();
573 | return null;
574 | });
575 | }
576 |
577 | @AsyncTest
578 | public function testAsyncChaining(factory : AsyncFactory) : Void {
579 | var task : Task = null;
580 | var tasks = new Array>();
581 |
582 | var sequence = new Array();
583 | var result : Task = Task.forResult(null);
584 |
585 | for (i in 0 ... 20) {
586 | result = result.continueWithTask(function(task : Task) : Task {
587 | return Task.call(function() : Nothing {
588 | sequence.push(i);
589 | return null;
590 | }, new TimerExecutor(randomInt(10, 50)));
591 | });
592 | }
593 |
594 | var handler : Dynamic = factory.createHandler(this, function() : Void {
595 | Assert.areEqual(20, sequence.length);
596 |
597 | for (i in 0 ... 20) {
598 | Assert.areEqual(i, sequence[i]);
599 | }
600 | }, 5000);
601 |
602 | result.continueWith(function(t : Task) : Nothing {
603 | task = t;
604 | handler();
605 | return null;
606 | });
607 | }
608 |
609 | @Test
610 | public function testOnSuccess() : Void {
611 | var continuation = function(task : Task) : Int {
612 | return task.result + 1;
613 | };
614 |
615 | var complete : Task = Task.forResult(5).onSuccess(continuation);
616 | var error : Task = Task.forError(new TestException()).onSuccess(continuation);
617 | var cancelled : Task = Task.cancelled().onSuccess(continuation);
618 |
619 | Assert.isTrue(complete.isCompleted);
620 | Assert.areEqual(6, complete.result);
621 | Assert.isFalse(complete.isFaulted);
622 | Assert.isFalse(complete.isCancelled);
623 | Assert.isTrue(complete.isSuccessed);
624 |
625 | Assert.isTrue(error.isCompleted);
626 | Assert.isType(error.error, TestException);
627 | Assert.isTrue(error.isFaulted);
628 | Assert.isFalse(error.isCancelled);
629 | Assert.isFalse(error.isSuccessed);
630 |
631 | Assert.isTrue(cancelled.isCompleted);
632 | Assert.isFalse(cancelled.isFaulted);
633 | Assert.isTrue(cancelled.isCancelled);
634 | Assert.isFalse(cancelled.isSuccessed);
635 | }
636 |
637 | @Test
638 | public function testOnSuccessTask() : Void {
639 | var continuation = function(task : Task) : Task {
640 | return Task.forResult(task.result + 1);
641 | };
642 |
643 | var complete : Task = Task.forResult(5).onSuccessTask(continuation);
644 | var error : Task = Task.forError(new TestException()).onSuccessTask(continuation);
645 | var cancelled : Task = Task.cancelled().onSuccessTask(continuation);
646 |
647 | Assert.isTrue(complete.isCompleted);
648 | Assert.areEqual(6, complete.result);
649 | Assert.isFalse(complete.isFaulted);
650 | Assert.isFalse(complete.isCancelled);
651 | Assert.isTrue(complete.isSuccessed);
652 |
653 | Assert.isTrue(error.isCompleted);
654 | Assert.isType(error.error, TestException);
655 | Assert.isTrue(error.isFaulted);
656 | Assert.isFalse(error.isCancelled);
657 | Assert.isFalse(error.isSuccessed);
658 |
659 | Assert.isTrue(cancelled.isCompleted);
660 | Assert.isFalse(cancelled.isFaulted);
661 | Assert.isTrue(cancelled.isCancelled);
662 | Assert.isFalse(cancelled.isSuccessed);
663 | }
664 |
665 | @Test
666 | public function testContinueWhile() : Void {
667 | var count : Int = 0;
668 | var handled : Bool = false;
669 |
670 | Task.forResult(null).continueWhile(function() : Bool {
671 | return (count < 10);
672 | }, function(task : Task) : Task {
673 | count++;
674 | return null;
675 | }).continueWith(function(task : Task) : Nothing {
676 | Assert.areEqual(10, count);
677 | handled = true;
678 | return null;
679 | });
680 |
681 | Assert.isTrue(handled);
682 | }
683 |
684 | @AsyncTest
685 | public function testContinueWhileAsync(factory : AsyncFactory) : Void {
686 | var count : Int = 0;
687 |
688 | var handler : Dynamic = factory.createHandler(this, function() : Void {
689 | Assert.areEqual(10, count);
690 | }, 15000);
691 |
692 | Task.forResult(null).continueWhile(function() : Bool {
693 | return (count < 10);
694 | }, function(task : Task) : Task {
695 | count++;
696 | return null;
697 | }, new TimerExecutor(10)).continueWith(function(task : Task) : Nothing {
698 | handler();
699 | return null;
700 | });
701 | }
702 |
703 | @Test
704 | public function testNullError() : Void {
705 | var error : Task = Task.forError(null);
706 |
707 | Assert.isTrue(error.isCompleted);
708 | Assert.areSame(error.error, null);
709 | Assert.isTrue(error.isFaulted);
710 | Assert.isFalse(error.isCancelled);
711 | Assert.isFalse(error.isSuccessed);
712 | }
713 |
714 | private function addTasksWithRandomCompletions(
715 | tasks : Array>,
716 | numberOfTasksToLaunch : Int,
717 | minDelay : Int = 100,
718 | maxDelay : Int = 200,
719 | minResult : Int = 0,
720 | maxResult : Int = 1000
721 | ) : Void {
722 | for (i in 0 ... numberOfTasksToLaunch) {
723 | tasks.push(Task.call(function() : Int {
724 | var rand : Float = Math.random();
725 |
726 | if (rand >= 0.7) {
727 | throw new TestException();
728 | } else if (rand >= 0.4) {
729 | throw new TaskCancellationException();
730 | }
731 |
732 | return randomInt(minResult, maxResult);
733 | }, new TimerExecutor(randomInt(minDelay, maxDelay))));
734 | }
735 | }
736 |
737 | private function randomInt(from : Int, to : Int) : Int {
738 | return from + Math.floor((to - from + 1) * Math.random());
739 | }
740 | }
741 |
--------------------------------------------------------------------------------
/demos/02-threads-openfl/assets/Intro.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------