├── 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 | [![BSD License](https://img.shields.io/badge/license-BSD-blue.svg?style=flat)](LICENSE) 2 | [![Haxe 3](https://img.shields.io/badge/language-Haxe%203-orange.svg)](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 | !"#$%&'()*+,-./0123456789:;å<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` abcdefghijklmnopqrstuvwxyz|{}~ --------------------------------------------------------------------------------