Skip to content

Commit ab1cc93

Browse files
committed
Implement Promise.completeAsync().
Compared to `new Future()`, this offers: - The option to run on the main thread. - The option to send progress events. - The ability to interrupt the job by calling `promise.error()`.
1 parent 4e39bf5 commit ab1cc93

File tree

3 files changed

+122
-19
lines changed

3 files changed

+122
-19
lines changed

src/lime/app/Future.hx

+30-7
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import lime.utils.Log;
8181
var promise = new Promise<T>();
8282
promise.future = this;
8383

84-
FutureWork.run(work, promise);
84+
FutureWork.runSimpleJob(work, promise);
8585
}
8686
else
8787
#end
@@ -308,7 +308,6 @@ import lime.utils.Log;
308308
}
309309
}
310310

311-
#if (lime_threads && !html5)
312311
/**
313312
The class that handles asynchronous `work` functions passed to `new Future()`.
314313
**/
@@ -319,26 +318,42 @@ import lime.utils.Log;
319318
@:dox(hide) class FutureWork
320319
{
321320
private static var threadPool:ThreadPool;
322-
private static var promises:Map<Int, {complete:Dynamic->Dynamic, error:Dynamic->Dynamic}>;
321+
private static var promises:Map<Int, {complete:Dynamic->Dynamic, error:Dynamic->Dynamic, progress:Int->Int->Dynamic}>;
323322

324323
public static var minThreads(default, set):Int = 0;
325324
public static var maxThreads(default, set):Int = 1;
326325
public static var activeJobs(get, never):Int;
327326

327+
@:allow(lime.app.Promise)
328+
private static inline function cancelJob(id:Int):Void
329+
{
330+
threadPool.cancelJob(id);
331+
}
332+
333+
#if (lime_threads && !html5)
328334
@:allow(lime.app.Future)
329-
private static function run<T>(work:Void->T, promise:Promise<T>):Void
335+
private static function runSimpleJob<T>(work:Void->T, promise:Promise<T>):Void
336+
{
337+
run(threadPool_doWork, promise, work, MULTI_THREADED);
338+
}
339+
#end
340+
341+
@:allow(lime.app.Promise)
342+
private static function run<T>(work:WorkFunction<State->WorkOutput->Void>, promise:Promise<T>, state:State, mode:ThreadMode):Int
330343
{
331344
if (threadPool == null)
332345
{
333346
threadPool = new ThreadPool(minThreads, maxThreads, MULTI_THREADED);
334347
threadPool.onComplete.add(threadPool_onComplete);
335348
threadPool.onError.add(threadPool_onError);
349+
threadPool.onProgress.add(threadPool_onProgress);
336350

337351
promises = new Map();
338352
}
339353

340-
var jobID:Int = threadPool.run(threadPool_doWork, work);
341-
promises[jobID] = {complete: promise.complete, error: promise.error};
354+
var jobID:Int = threadPool.run(work, state, mode);
355+
promises[jobID] = {complete: promise.complete, error: promise.error, progress: promise.progress};
356+
return jobID;
342357
}
343358

344359
// Event Handlers
@@ -368,6 +383,15 @@ import lime.utils.Log;
368383
promise.error(error);
369384
}
370385

386+
private static function threadPool_onProgress(progress:{progress:Int, total:Int}):Void
387+
{
388+
// ThreadPool doesn't enforce types, so check manually
389+
if (Type.typeof(progress) == TObject && Type.typeof(progress.progress) == TInt && Type.typeof(progress.total) == TInt)
390+
{
391+
promises[threadPool.activeJob.id].progress(progress.progress, progress.total);
392+
}
393+
}
394+
371395
// Getters & Setters
372396
@:noCompletion private static inline function set_minThreads(value:Int):Int
373397
{
@@ -392,4 +416,3 @@ import lime.utils.Log;
392416
return threadPool != null ? threadPool.activeJobs : 0;
393417
}
394418
}
395-
#end

src/lime/app/Promise.hx

+88-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package lime.app;
22

3+
import lime.app.Future;
4+
import lime.system.ThreadPool;
5+
import lime.system.WorkOutput;
6+
37
/**
48
`Promise` is an implementation of Futures and Promises, with the exception that
59
in addition to "success" and "failure" states (represented as "complete" and "error"),
@@ -10,18 +14,20 @@ package lime.app;
1014
for recipients of it's `Future` object. For example:
1115
1216
```haxe
13-
function examplePromise ():Future<String> {
14-
15-
var promise = new Promise<String> ();
17+
function examplePromise():Future<String>
18+
{
19+
var promise = new Promise<String>();
1620
1721
var progress = 0, total = 10;
18-
var timer = new Timer (100);
19-
timer.run = function () {
22+
var timer = new Timer(100);
23+
timer.run = function()
24+
{
2025
2126
promise.progress (progress, total);
2227
progress++;
2328
24-
if (progress == total) {
29+
if (progress == total)
30+
{
2531
2632
promise.complete ("Done!");
2733
timer.stop ();
@@ -31,12 +37,11 @@ package lime.app;
3137
};
3238
3339
return promise.future;
34-
3540
}
3641
37-
var future = examplePromise ();
38-
future.onComplete (function (message) { trace (message); });
39-
future.onProgress (function (loaded, total) { trace ("Progress: " + loaded + ", " + total); });
42+
var future = examplePromise();
43+
future.onComplete(function(message) { trace(message); });
44+
future.onProgress(function(loaded, total) { trace("Progress: " + loaded + ", " + total); });
4045
```
4146
**/
4247
#if !lime_debug
@@ -69,6 +74,8 @@ class Promise<T>
6974
**/
7075
public var isError(get, null):Bool;
7176

77+
private var jobID:Int = -1;
78+
7279
#if commonjs
7380
private static function __init__()
7481
{
@@ -96,11 +103,23 @@ class Promise<T>
96103
**/
97104
public function complete(data:T):Promise<T>
98105
{
106+
if (!ThreadPool.isMainThread())
107+
{
108+
haxe.MainLoop.runInMainThread(complete.bind(data));
109+
return this;
110+
}
111+
99112
if (!future.isError)
100113
{
101114
future.isComplete = true;
102115
future.value = data;
103116

117+
if (jobID != -1)
118+
{
119+
FutureWork.cancelJob(jobID);
120+
jobID = -1;
121+
}
122+
104123
if (future.__completeListeners != null)
105124
{
106125
for (listener in future.__completeListeners)
@@ -115,6 +134,45 @@ class Promise<T>
115134
return this;
116135
}
117136

137+
/**
138+
Runs the given function asynchronously, and resolves this `Promise` with
139+
the complete, error, and/or progress events sent by that function.
140+
Sample usage:
141+
142+
```haxe
143+
function examplePromise():Future<String>
144+
{
145+
var promise = new Promise<String>();
146+
promise.completeAsync(function(state:State, output:WorkOutput):Void
147+
{
148+
output.sendProgress({progress:state.progress, total:10});
149+
state.progress++;
150+
151+
if (state.progress == 10)
152+
{
153+
output.sendComplete("Done!");
154+
}
155+
},
156+
{progress: 0}, MULTI_THREADED);
157+
158+
return promise.future;
159+
}
160+
161+
var future = examplePromise();
162+
future.onComplete(function(message) { trace(message); });
163+
future.onProgress(function(loaded, total) { trace("Progress: " + loaded + ", " + total); });
164+
```
165+
166+
@param doWork A function to perform work asynchronously. For best results,
167+
see the guidelines in the `ThreadPool` class overview.
168+
@param state The value to pass to `doWork`.
169+
@param mode Which mode to run the job in: `SINGLE_THREADED` or `MULTI_THREADED`.
170+
**/
171+
public function completeAsync(doWork:WorkFunction<State->WorkOutput->Void>, ?state:State, ?mode:ThreadMode = MULTI_THREADED):Void
172+
{
173+
jobID = FutureWork.run(doWork, this, state, mode);
174+
}
175+
118176
/**
119177
Resolves this `Promise` with the complete, error and/or progress state
120178
of another `Future`
@@ -137,11 +195,23 @@ class Promise<T>
137195
**/
138196
public function error(msg:Dynamic):Promise<T>
139197
{
198+
if (!ThreadPool.isMainThread())
199+
{
200+
haxe.MainLoop.runInMainThread(error.bind(msg));
201+
return this;
202+
}
203+
140204
if (!future.isComplete)
141205
{
142206
future.isError = true;
143207
future.error = msg;
144208

209+
if (jobID != -1)
210+
{
211+
FutureWork.cancelJob(jobID);
212+
jobID = -1;
213+
}
214+
145215
if (future.__errorListeners != null)
146216
{
147217
for (listener in future.__errorListeners)
@@ -164,6 +234,12 @@ class Promise<T>
164234
**/
165235
public function progress(progress:Int, total:Int):Promise<T>
166236
{
237+
if (!ThreadPool.isMainThread())
238+
{
239+
haxe.MainLoop.runInMainThread(this.progress.bind(progress, total));
240+
return this;
241+
}
242+
167243
if (!future.isError && !future.isComplete)
168244
{
169245
if (future.__progressListeners != null)
@@ -179,12 +255,12 @@ class Promise<T>
179255
}
180256

181257
// Get & Set Methods
182-
@:noCompletion private function get_isComplete():Bool
258+
@:noCompletion private inline function get_isComplete():Bool
183259
{
184260
return future.isComplete;
185261
}
186262

187-
@:noCompletion private function get_isError():Bool
263+
@:noCompletion private inline function get_isError():Bool
188264
{
189265
return future.isError;
190266
}

src/lime/system/WorkOutput.hx

+4
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ class JobData
324324
private inline function new(doWork:WorkFunction<State->WorkOutput->Void>, state:State, ?id:Int)
325325
{
326326
this.id = id != null ? id : nextID++;
327+
if (this.id == -1)
328+
{
329+
throw "All job IDs have been used!";
330+
}
327331
this.doWork = doWork;
328332
this.state = state;
329333
}

0 commit comments

Comments
 (0)