From 432f48d4fdc30c15572fb63a5c0a74953d9ce53c Mon Sep 17 00:00:00 2001 From: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:55:00 +0300 Subject: [PATCH 01/10] Define `PLATFORM` and `PLATFORM_NUMBER` based on the minimum `Android` SDK Version. --- tools/platforms/AndroidPlatform.hx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tools/platforms/AndroidPlatform.hx b/tools/platforms/AndroidPlatform.hx index 4d4caaaf12..165f2c0990 100644 --- a/tools/platforms/AndroidPlatform.hx +++ b/tools/platforms/AndroidPlatform.hx @@ -161,8 +161,9 @@ class AndroidPlatform extends PlatformTarget for (architecture in architectures) { - var haxeParams = [hxml, "-D", "android", "-D", "PLATFORM=android-21"]; - var cppParams = ["-Dandroid", "-DPLATFORM=android-21"]; + var minimumSDKVersion = project.config.getInt("android.minimum-sdk-version", 21); + var haxeParams = [hxml, "-D", "android", "-D", "PLATFORM=android-" + minimumSDKVersion, "-D", "PLATFORM_NUMBER=" + minimumSDKVersion]; + var cppParams = ["-Dandroid", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]; var path = sourceSet + "/jniLibs/armeabi"; var suffix = ".so"; @@ -363,21 +364,22 @@ class AndroidPlatform extends PlatformTarget public override function rebuild():Void { - var armv5 = (/*command == "rebuild" ||*/ - ArrayTools.containsValue(project.architectures, Architecture.ARMV5) - || ArrayTools.containsValue(project.architectures, Architecture.ARMV6)); + var armv5 = ArrayTools.containsValue(project.architectures, Architecture.ARMV5) + || ArrayTools.containsValue(project.architectures, Architecture.ARMV6); var armv7 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.ARMV7)); var arm64 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.ARM64)); var x86 = (command == "rebuild" || ArrayTools.containsValue(project.architectures, Architecture.X86)); - var x64 = (/*command == "rebuild" ||*/ ArrayTools.containsValue(project.architectures, Architecture.X64)); + var x64 = ArrayTools.containsValue(project.architectures, Architecture.X64); var commands = []; - if (armv5) commands.push(["-Dandroid", "-DPLATFORM=android-21"]); - if (armv7) commands.push(["-Dandroid", "-DHXCPP_ARMV7", "-DPLATFORM=android-21"]); - if (arm64) commands.push(["-Dandroid", "-DHXCPP_ARM64", "-DPLATFORM=android-21"]); - if (x86) commands.push(["-Dandroid", "-DHXCPP_X86", "-DPLATFORM=android-21"]); - if (x64) commands.push(["-Dandroid", "-DHXCPP_X86_64", "-DPLATFORM=android-21"]); + var minimumSDKVersion = project.config.getInt("android.minimum-sdk-version", 21); + + if (armv5) commands.push(["-Dandroid", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]); + if (armv7) commands.push(["-Dandroid", "-DHXCPP_ARMV7", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]); + if (arm64) commands.push(["-Dandroid", "-DHXCPP_ARM64", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]); + if (x86) commands.push(["-Dandroid", "-DHXCPP_X86", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]); + if (x64) commands.push(["-Dandroid", "-DHXCPP_X86_64", "-DPLATFORM=android-" + minimumSDKVersion, "-DPLATFORM_NUMBER=" + minimumSDKVersion]); CPPHelper.rebuild(project, commands); } From dae33c0c1a2965f76e33bdd4f216c440ca23d80b Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Fri, 9 Aug 2024 15:11:50 -0400 Subject: [PATCH 02/10] Bug fix: new job could be ignored if it arrived with precise timing. --- src/lime/system/ThreadPool.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index 7fd6561b95..4a3bae7ef7 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -438,7 +438,7 @@ class ThreadPool extends WorkOutput if (interruption == null || output.__jobComplete.value) { // Work is done; wait for more. - event = null; + event = interruption; } else if(#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (interruption, ThreadEvent)) { From bf4711a01d7a3cd9bb8e2bdf86fa653bdea356d5 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Fri, 9 Aug 2024 15:58:48 -0400 Subject: [PATCH 03/10] Avoid sending `JobData` back to the main thread. The main thread can easily look these up by ID, and in HTML5, sending the full `JobData` can cause errors. --- src/lime/app/Future.hx | 6 ---- src/lime/system/ThreadPool.hx | 36 ++++++++++++----------- src/lime/system/WorkOutput.hx | 54 ++++++++++------------------------- 3 files changed, 35 insertions(+), 61 deletions(-) diff --git a/src/lime/app/Future.hx b/src/lime/app/Future.hx index 2af97182d4..780a243b08 100644 --- a/src/lime/app/Future.hx +++ b/src/lime/app/Future.hx @@ -416,17 +416,11 @@ import lime.utils.Log; var result = bundle.work.dispatch(bundle.state); if (result != null || bundle.legacyCode) { - #if (lime_threads && html5) - bundle.work.makePortable(); - #end output.sendComplete(result); } } catch (e:Dynamic) { - #if (lime_threads && html5) - bundle.work.makePortable(); - #end output.sendError(e); } } diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index 4a3bae7ef7..f997abe69e 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -245,12 +245,12 @@ class ThreadPool extends WorkOutput var thread:Thread = __activeThreads[job.id]; if (idleThreads < minThreads) { - thread.sendMessage(new ThreadEvent(WORK, null, null)); + thread.sendMessage({event: CANCEL}); __idleThreads.push(thread); } else { - thread.sendMessage(new ThreadEvent(EXIT, null, null)); + thread.sendMessage({event: EXIT}); } } #end @@ -270,10 +270,10 @@ class ThreadPool extends WorkOutput __activeJobs.clear(); #if lime_threads - // Cancel idle threads if there are more than the minimum. + // Exit idle threads if there are more than the minimum. while (idleThreads > minThreads) { - __idleThreads.pop().sendMessage(new ThreadEvent(EXIT, null, null)); + __idleThreads.pop().sendMessage({event: EXIT}); } #end @@ -310,7 +310,7 @@ class ThreadPool extends WorkOutput var thread:Thread = __activeThreads[data.id]; if (thread != null) { - thread.sendMessage(new ThreadEvent(WORK, null, null)); + thread.sendMessage({event: CANCEL}); __activeThreads.remove(data.id); __idleThreads.push(thread); } @@ -395,7 +395,7 @@ class ThreadPool extends WorkOutput { event = Thread.readMessage(true); } - while (!#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (event, ThreadEvent)); + while (event == null || !Reflect.hasField(event, "event")); output.resetJobProgress(); } @@ -409,7 +409,7 @@ class ThreadPool extends WorkOutput return; } - if (event.event != WORK || event.job == null) + if (event.event != WORK || !#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (event.job, JobData)) { // Go idle. event = null; @@ -440,7 +440,7 @@ class ThreadPool extends WorkOutput // Work is done; wait for more. event = interruption; } - else if(#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (interruption, ThreadEvent)) + else if(Reflect.hasField(interruption, "event")) { // Work on the new job. event = interruption; @@ -494,7 +494,7 @@ class ThreadPool extends WorkOutput var thread:Thread = __idleThreads.isEmpty() ? createThread(__executeThread) : __idleThreads.pop(); __activeThreads[job.id] = thread; - thread.sendMessage(new ThreadEvent(WORK, null, job)); + thread.sendMessage({event: WORK, job: job}); } #end } @@ -539,15 +539,19 @@ class ThreadPool extends WorkOutput var threadEvent:ThreadEvent; while ((threadEvent = __jobOutput.pop(false)) != null) { - if (!__activeJobs.exists(threadEvent.job)) + if (threadEvent.jobID != null) { - // Ignore events from canceled jobs. - continue; + activeJob = __activeJobs.getByID(threadEvent.jobID); + } + else + { + activeJob = threadEvent.job; } - // Get by ID because in HTML5, the object will have been cloned, - // which will interfere with attempts to test equality. - activeJob = __activeJobs.getByID(threadEvent.job.id); + if (activeJob == null || !__activeJobs.exists(activeJob)) + { + continue; + } if (mode == MULTI_THREADED) { @@ -582,7 +586,7 @@ class ThreadPool extends WorkOutput if (currentThreads > maxThreads || __jobQueue.length == 0 && currentThreads > minThreads) { - thread.sendMessage(new ThreadEvent(EXIT, null, null)); + thread.sendMessage({event: EXIT}); } else { diff --git a/src/lime/system/WorkOutput.hx b/src/lime/system/WorkOutput.hx index 433fad10e3..a35b5fb71c 100644 --- a/src/lime/system/WorkOutput.hx +++ b/src/lime/system/WorkOutput.hx @@ -105,12 +105,11 @@ class WorkOutput #if (lime_threads && html5) if (mode == MULTI_THREADED) { - activeJob.doWork.makePortable(); - Thread.returnMessage(new ThreadEvent(COMPLETE, message, activeJob), transferList); + Thread.returnMessage({event: COMPLETE, message: message, jobID: activeJob.id}, transferList); } else #end - __jobOutput.add(new ThreadEvent(COMPLETE, message, activeJob)); + __jobOutput.add({event: COMPLETE, message: message, jobID: activeJob.id}); } } @@ -130,12 +129,11 @@ class WorkOutput #if (lime_threads && html5) if (mode == MULTI_THREADED) { - activeJob.doWork.makePortable(); - Thread.returnMessage(new ThreadEvent(ERROR, message, activeJob), transferList); + Thread.returnMessage({event: ERROR, message: message, jobID: activeJob.id}, transferList); } else #end - __jobOutput.add(new ThreadEvent(ERROR, message, activeJob)); + __jobOutput.add({event: ERROR, message: message, jobID: activeJob.id}); } } @@ -153,12 +151,11 @@ class WorkOutput #if (lime_threads && html5) if (mode == MULTI_THREADED) { - activeJob.doWork.makePortable(); - Thread.returnMessage(new ThreadEvent(PROGRESS, message, activeJob), transferList); + Thread.returnMessage({event: PROGRESS, message: message, jobID: activeJob.id}, transferList); } else #end - __jobOutput.add(new ThreadEvent(PROGRESS, message, activeJob)); + __jobOutput.add({event: PROGRESS, message: message, jobID: activeJob.id}); } } @@ -343,43 +340,22 @@ class JobData #if haxe4 enum #else @:enum #end abstract ThreadEventType(String) { - /** - Sent by the background thread, indicating completion. - **/ + // Events sent from a worker thread to the main thread var COMPLETE = "COMPLETE"; - /** - Sent by the background thread, indicating failure. - **/ var ERROR = "ERROR"; - /** - Sent by the background thread. - **/ var PROGRESS = "PROGRESS"; - /** - Sent by the main thread, indicating that the provided job should begin - in place of any ongoing job. If `state == null`, the existing job will - stop and the thread will go idle. (To run a job with no argument, set - `state = {}` instead.) - **/ + + // Commands sent from the main thread to a worker thread var WORK = "WORK"; - /** - Sent by the main thread to shut down a thread. - **/ + var CANCEL = "CANCEL"; var EXIT = "EXIT"; } -class ThreadEvent -{ - public var event(default, null):ThreadEventType; - public var message(default, null):State; - public var job(default, null):JobData; - - public inline function new(event:ThreadEventType, message:State, job:JobData) - { - this.event = event; - this.message = message; - this.job = job; - } +typedef ThreadEvent = { + var event:ThreadEventType; + @:optional var message:Dynamic; + @:optional var job:JobData; + @:optional var jobID:Int; } class JSAsync From 0b83b7d45ef18b4bb9a77ae37f18d6c5169e3a83 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Sun, 11 Aug 2024 16:11:19 -0400 Subject: [PATCH 04/10] Fix typo. --- src/lime/_internal/backend/html5/HTML5Thread.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lime/_internal/backend/html5/HTML5Thread.hx b/src/lime/_internal/backend/html5/HTML5Thread.hx index 845a2924d7..022a085da1 100644 --- a/src/lime/_internal/backend/html5/HTML5Thread.hx +++ b/src/lime/_internal/backend/html5/HTML5Thread.hx @@ -477,7 +477,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic // Skip `null` for obvious reasons. return object == null // No need to preserve a primitive type. - || !#if (haxe_ver >= 4.2) Std.isOfType #else untyped __js__ #end (object, Object) + || !#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object, Object) // Objects with this field have been deliberately excluded. || Reflect.field(object, SKIP_FIELD) == true // A `Uint8Array` (the type used by `haxe.io.Bytes`) can have From 8f631fe3ad2d7ad256ff2fafb7cb00f7dac9335c Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Sun, 11 Aug 2024 21:35:12 -0400 Subject: [PATCH 05/10] Optimize `ThreadPool` slightly. `__activeThreads` and `__idleThreads` only need to be allocated for multi-threaded pools. Plus, there's no benefit to using a `List` here; we only add to and remove from the end. And finally, checking `event.job == null` instead of `isOfType()` is faster and avoids an issue in HTML5. Sadly it is less safe, so we might need to revisit it eventually. --- src/lime/system/ThreadPool.hx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index f997abe69e..c33390a008 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -188,13 +188,13 @@ class ThreadPool extends WorkOutput /** The set of threads actively running a job. **/ - private var __activeThreads:Map = new Map(); + private var __activeThreads:Map; /** A list of idle threads. Not to be confused with `idleThreads`, a public variable equal to `__idleThreads.length`. **/ - private var __idleThreads:List = new List(); + private var __idleThreads:Array; #end private var __jobQueue:JobList = new JobList(); @@ -219,6 +219,14 @@ class ThreadPool extends WorkOutput this.minThreads = minThreads; this.maxThreads = maxThreads; + + #if lime_threads + if (this.mode == MULTI_THREADED) + { + __activeThreads = new Map(); + __idleThreads = []; + } + #end } /** @@ -409,7 +417,7 @@ class ThreadPool extends WorkOutput return; } - if (event.event != WORK || !#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (event.job, JobData)) + if (event.event != WORK || event.job == null) { // Go idle. event = null; @@ -492,7 +500,7 @@ class ThreadPool extends WorkOutput job.doWork.makePortable(); #end - var thread:Thread = __idleThreads.isEmpty() ? createThread(__executeThread) : __idleThreads.pop(); + var thread:Thread = __idleThreads.length == 0 ? createThread(__executeThread) : __idleThreads.pop(); __activeThreads[job.id] = thread; thread.sendMessage({event: WORK, job: job}); } From d0cef427bcfec2f4f4cb6b5cd019e51d6d33181a Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Thu, 15 Aug 2024 15:39:07 -0400 Subject: [PATCH 06/10] Revert `BackgroundWorker` to its 8.1.3 version. It looks like we'll want to take `BackgroundWorker` in a different direction, so for the moment it's safest not to change anything about it. That way, there's only one historical version to maintain backwards compatibility with. --- src/lime/system/BackgroundWorker.hx | 177 +++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 2 deletions(-) diff --git a/src/lime/system/BackgroundWorker.hx b/src/lime/system/BackgroundWorker.hx index 5be17e9eae..ba1b7340a0 100644 --- a/src/lime/system/BackgroundWorker.hx +++ b/src/lime/system/BackgroundWorker.hx @@ -1,4 +1,177 @@ package lime.system; -@:deprecated("Replace references to lime.system.BackgroundWorker with lime.system.ThreadPool. As the API is identical, no other changes are necessary.") -typedef BackgroundWorker = ThreadPool; +import lime.app.Application; +import lime.app.Event; +#if sys +#if haxe4 +import sys.thread.Deque; +import sys.thread.Thread; +#elseif cpp +import cpp.vm.Deque; +import cpp.vm.Thread; +#elseif neko +import neko.vm.Deque; +import neko.vm.Thread; +#end +#end +#if !lime_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end + +/** + A background worker executes a single function on a background thread, + allowing it to avoid blocking the main thread. However, only system targets + have thread support, meaning the function will block on any other target. + @see `ThreadPool` for improved thread safety, HTML5 threads, and more. +**/ +class BackgroundWorker +{ + private static var MESSAGE_COMPLETE = "__COMPLETE__"; + private static var MESSAGE_ERROR = "__ERROR__"; + + public var canceled(default, null):Bool; + public var completed(default, null):Bool; + public var doWork = new EventVoid>(); + public var onComplete = new EventVoid>(); + public var onError = new EventVoid>(); + public var onProgress = new EventVoid>(); + + @:noCompletion private var __runMessage:Dynamic; + #if (cpp || neko) + @:noCompletion private var __messageQueue:Deque; + @:noCompletion private var __workerThread:Thread; + #end + + public function new() {} + + public function cancel():Void + { + canceled = true; + + #if (cpp || neko) + __workerThread = null; + #end + } + + public function run(message:Dynamic = null):Void + { + canceled = false; + completed = false; + __runMessage = message; + + #if (cpp || neko) + __messageQueue = new Deque(); + __workerThread = Thread.create(__doWork); + + // TODO: Better way to do this + + if (Application.current != null) + { + Application.current.onUpdate.add(__update); + } + #else + __doWork(); + #end + } + + public function sendComplete(message:Dynamic = null):Void + { + completed = true; + + #if (cpp || neko) + __messageQueue.add(MESSAGE_COMPLETE); + __messageQueue.add(message); + #else + if (!canceled) + { + canceled = true; + onComplete.dispatch(message); + } + #end + } + + public function sendError(message:Dynamic = null):Void + { + #if (cpp || neko) + __messageQueue.add(MESSAGE_ERROR); + __messageQueue.add(message); + #else + if (!canceled) + { + canceled = true; + onError.dispatch(message); + } + #end + } + + public function sendProgress(message:Dynamic = null):Void + { + #if (cpp || neko) + __messageQueue.add(message); + #else + if (!canceled) + { + onProgress.dispatch(message); + } + #end + } + + @:noCompletion private function __doWork():Void + { + doWork.dispatch(__runMessage); + + // #if (cpp || neko) + // + // __messageQueue.add (MESSAGE_COMPLETE); + // + // #else + // + // if (!canceled) { + // + // canceled = true; + // onComplete.dispatch (null); + // + // } + // + // #end + } + + @:noCompletion private function __update(deltaTime:Int):Void + { + #if (cpp || neko) + var message = __messageQueue.pop(false); + + if (message != null) + { + if (message == MESSAGE_ERROR) + { + Application.current.onUpdate.remove(__update); + + if (!canceled) + { + canceled = true; + onError.dispatch(__messageQueue.pop(false)); + } + } + else if (message == MESSAGE_COMPLETE) + { + Application.current.onUpdate.remove(__update); + + if (!canceled) + { + canceled = true; + onComplete.dispatch(__messageQueue.pop(false)); + } + } + else + { + if (!canceled) + { + onProgress.dispatch(message); + } + } + } + #end + } +} From 52931a8dc704f3cc2686381885f23159fd0b45bf Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Thu, 15 Aug 2024 16:16:17 -0400 Subject: [PATCH 07/10] Revert to `Future`'s behavior from 8.1.3. As with `BackgroundWorker`, we're postponing major changes to give us more time to consider. --- src/lime/app/Future.hx | 216 +++++++++------------------------- src/lime/graphics/Image.hx | 2 +- src/lime/media/AudioBuffer.hx | 2 +- 3 files changed, 55 insertions(+), 165 deletions(-) diff --git a/src/lime/app/Future.hx b/src/lime/app/Future.hx index 780a243b08..8fb51d96c3 100644 --- a/src/lime/app/Future.hx +++ b/src/lime/app/Future.hx @@ -67,24 +67,36 @@ import lime.utils.Log; @:noCompletion private var __progressListeners:ArrayInt->Void>; /** - @param work Deprecated; use `Future.withEventualValue()` instead. - @param useThreads Deprecated; use `Future.withEventualValue()` instead. + @param work Optional: a function to compute this future's value. + @param useThreads Whether to run `work` on a background thread, where supported. + If false or if this isn't a system target, it will run immediately on the main thread. **/ - public function new(work:WorkFunctionT> = null, useThreads:Bool = false) + public function new(work:Void->T = null, useThreads:Bool = false) { if (work != null) { - var promise = new Promise(); - promise.future = this; - - #if (lime_threads && html5) + #if (lime_threads && !html5) if (useThreads) { - work.makePortable(); + var promise = new Promise(); + promise.future = this; + + FutureWork.run(work, promise); } + else #end - - FutureWork.run(dispatchWorkFunction, work, promise, useThreads ? MULTI_THREADED : SINGLE_THREADED, true); + { + try + { + value = work(); + isComplete = true; + } + catch (e:Dynamic) + { + error = e; + isError = true; + } + } } } @@ -196,29 +208,14 @@ import lime.utils.Log; else { var time = System.getTimer(); - var prevTime = time; var end = time + waitTime; - while (!isComplete && !isError && time <= end) + while (!isComplete && !isError && time <= end && FutureWork.activeJobs > 0) { - if (FutureWork.activeJobs < 1) - { - Log.error('Cannot block for a Future without a "work" function.'); - return this; - } - - if (FutureWork.singleThreadPool != null && FutureWork.singleThreadPool.activeJobs > 0) - { - @:privateAccess FutureWork.singleThreadPool.__update(time - prevTime); - } - else - { - #if sys - Sys.sleep(0.01); - #end - } + #if sys + Sys.sleep(0.01); + #end - prevTime = time; time = System.getTimer(); } @@ -305,41 +302,9 @@ import lime.utils.Log; future.value = value; return future; } - - /** - Creates a `Future` instance which will asynchronously compute a value. - - Once `work()` returns a non-null value, the `Future` will finish with that value. - If `work()` throws an error, the `Future` will finish with that error instead. - @param work A function that computes a value of type `T`. - @param state An argument to pass to `work()`. As this may be used on another thread, the - main thread must not access or modify `state` until the `Future` finishes. - @param mode Whether to use real threads as opposed to green threads. Green threads rely - on cooperative multitasking, meaning `work()` must return periodically to allow other code - enough time to run. In these cases, `work()` should return null to signal that it isn't finished. - @return A new `Future` instance. - @see https://en.wikipedia.org/wiki/Cooperative_multitasking - **/ - public static function withEventualValue(work:WorkFunction Null>, state:State, mode:ThreadMode = #if html5 SINGLE_THREADED #else MULTI_THREADED #end):Future - { - var future = new Future(); - var promise = new Promise(); - promise.future = future; - - FutureWork.run(work, state, promise, mode); - - return future; - } - - /** - (For backwards compatibility.) Dispatches the given zero-argument function. - **/ - @:noCompletion private static function dispatchWorkFunction(work:WorkFunction T>):Null - { - return work.dispatch(); - } } +#if (lime_threads && !html5) /** The class that handles asynchronous `work` functions passed to `new Future()`. **/ @@ -349,75 +314,34 @@ import lime.utils.Log; #end @:dox(hide) class FutureWork { - @:allow(lime.app.Future) - private static var singleThreadPool:ThreadPool; - #if lime_threads - private static var multiThreadPool:ThreadPool; - // It isn't safe to pass a promise object to a web worker, but since it's - // `@:generic` we can't store it as `Promise`. Instead, we'll store - // the two methods we need. - private static var promises:Map Dynamic, error:Dynamic -> Dynamic}> = new Map(); - #end + private static var threadPool:ThreadPool; + private static var promises:Map Dynamic, error:Dynamic -> Dynamic}>; + public static var minThreads(default, set):Int = 0; public static var maxThreads(default, set):Int = 1; public static var activeJobs(get, never):Int; - private static function getPool(mode:ThreadMode):ThreadPool - { - #if lime_threads - if (mode == MULTI_THREADED) { - if(multiThreadPool == null) { - multiThreadPool = new ThreadPool(minThreads, maxThreads, MULTI_THREADED); - multiThreadPool.onComplete.add(multiThreadPool_onComplete); - multiThreadPool.onError.add(multiThreadPool_onError); - } - return multiThreadPool; - } - #end - if(singleThreadPool == null) { - singleThreadPool = new ThreadPool(minThreads, maxThreads, SINGLE_THREADED); - singleThreadPool.onComplete.add(singleThreadPool_onComplete); - singleThreadPool.onError.add(singleThreadPool_onError); - } - return singleThreadPool; - } - @:allow(lime.app.Future) - private static function run(work:WorkFunctionNull>, state:State, promise:Promise, mode:ThreadMode = MULTI_THREADED, legacyCode:Bool = false):Void + private static function run(work:Void->T, promise:Promise):Void { - var bundle = {work: work, state: state, promise: promise, legacyCode: legacyCode}; + if(threadPool == null) { + threadPool = new ThreadPool(minThreads, maxThreads, MULTI_THREADED); + threadPool.onComplete.add(threadPool_onComplete); + threadPool.onError.add(threadPool_onError); - #if lime_threads - if (mode == MULTI_THREADED) - { - #if html5 - work.makePortable(); - #end - - bundle.promise = null; + promises = new Map(); } - #end - - var jobID:Int = getPool(mode).run(threadPool_doWork, bundle); - #if lime_threads - if (mode == MULTI_THREADED) - { - promises[jobID] = {complete: promise.complete, error: promise.error}; - } - #end + var jobID:Int = threadPool.run(threadPool_doWork, work); + promises[jobID] = {complete: promise.complete, error: promise.error}; } // Event Handlers - private static function threadPool_doWork(bundle:{work:WorkFunctionDynamic>, state:State, legacyCode:Bool}, output:WorkOutput):Void + private static function threadPool_doWork(work:Void->Dynamic, output:WorkOutput):Void { try { - var result = bundle.work.dispatch(bundle.state); - if (result != null || bundle.legacyCode) - { - output.sendComplete(result); - } + output.sendComplete(work()); } catch (e:Dynamic) { @@ -425,76 +349,42 @@ import lime.utils.Log; } } - private static function singleThreadPool_onComplete(result:Dynamic):Void + private static function threadPool_onComplete(result:Dynamic):Void { - singleThreadPool.activeJob.state.promise.complete(result); - } - - private static function singleThreadPool_onError(error:Dynamic):Void - { - singleThreadPool.activeJob.state.promise.error(error); - } - - #if lime_threads - private static function multiThreadPool_onComplete(result:Dynamic):Void - { - var promise = promises[multiThreadPool.activeJob.id]; - promises.remove(multiThreadPool.activeJob.id); + var promise = promises[threadPool.activeJob.id]; + promises.remove(threadPool.activeJob.id); promise.complete(result); } - private static function multiThreadPool_onError(error:Dynamic):Void + private static function threadPool_onError(error:Dynamic):Void { - var promise = promises[multiThreadPool.activeJob.id]; - promises.remove(multiThreadPool.activeJob.id); + var promise = promises[threadPool.activeJob.id]; + promises.remove(threadPool.activeJob.id); promise.error(error); } - #end // Getters & Setters @:noCompletion private static inline function set_minThreads(value:Int):Int { - if (singleThreadPool != null) - { - singleThreadPool.minThreads = value; - } - #if lime_threads - if (multiThreadPool != null) + if (threadPool != null) { - multiThreadPool.minThreads = value; + threadPool.minThreads = value; } - #end return minThreads = value; } @:noCompletion private static inline function set_maxThreads(value:Int):Int { - if (singleThreadPool != null) + if (threadPool != null) { - singleThreadPool.maxThreads = value; + threadPool.maxThreads = value; } - #if lime_threads - if (multiThreadPool != null) - { - multiThreadPool.maxThreads = value; - } - #end return maxThreads = value; } - @:noCompletion private static function get_activeJobs():Int + @:noCompletion private static inline function get_activeJobs():Int { - var sum:Int = 0; - if (singleThreadPool != null) - { - sum += singleThreadPool.activeJobs; - } - #if lime_threads - if (multiThreadPool != null) - { - sum += multiThreadPool.activeJobs; - } - #end - return sum; + return threadPool != null ? threadPool.activeJobs : 0; } } +#end diff --git a/src/lime/graphics/Image.hx b/src/lime/graphics/Image.hx index c825d9d84d..97385cdc3a 100644 --- a/src/lime/graphics/Image.hx +++ b/src/lime/graphics/Image.hx @@ -1002,7 +1002,7 @@ class Image return promise.future; #else - return Future.withEventualValue(fromBytes, bytes, MULTI_THREADED); + return new Future(fromBytes.bind(bytes), true); #end } diff --git a/src/lime/media/AudioBuffer.hx b/src/lime/media/AudioBuffer.hx index ff067229ab..967c8fbbe9 100644 --- a/src/lime/media/AudioBuffer.hx +++ b/src/lime/media/AudioBuffer.hx @@ -335,7 +335,7 @@ class AudioBuffer return promise.future; #else - return Future.withEventualValue(fromFiles, paths, MULTI_THREADED); + return new Future(fromFiles.bind(paths), true); #end } From 2866d099a2b094cec3890aece7a6a9f66dccdd85 Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Thu, 15 Aug 2024 16:31:48 -0400 Subject: [PATCH 08/10] Remove external link. While I put a lot of effort into that guide, we're changing several things suddenly, and I don't have time to make sure it's up to date. --- src/lime/system/ThreadPool.hx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lime/system/ThreadPool.hx b/src/lime/system/ThreadPool.hx index c33390a008..1f1ef98dac 100644 --- a/src/lime/system/ThreadPool.hx +++ b/src/lime/system/ThreadPool.hx @@ -37,9 +37,7 @@ import lime._internal.backend.html5.HTML5Thread as Thread; `WorkOutput` object it receives. Calling `output.sendComplete()` will trigger an `onComplete` event on the main thread. - @see `lime.system.WorkOutput.WorkFunction` for important information about - `doWork`. - @see https://player03.com/openfl/threads-guide/ for a tutorial. + @see `lime.system.WorkOutput.WorkFunction` for important information about `doWork`. **/ #if !lime_debug @:fileXml('tags="haxe,release"') From 6873ae1fd4c763a01e2a252cfcadcc12f54e303b Mon Sep 17 00:00:00 2001 From: Joseph Cloutier Date: Thu, 15 Aug 2024 16:44:52 -0400 Subject: [PATCH 09/10] `Future.ready()` only works when threads are available. --- src/lime/app/Future.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lime/app/Future.hx b/src/lime/app/Future.hx index 8fb51d96c3..d409dced8e 100644 --- a/src/lime/app/Future.hx +++ b/src/lime/app/Future.hx @@ -201,6 +201,7 @@ import lime.utils.Log; **/ public function ready(waitTime:Int = -1):Future { + #if (lime_threads && !html5) if (isComplete || isError) { return this; @@ -221,6 +222,9 @@ import lime.utils.Log; return this; } + #else + return this; + #end } /** From ed05aa267407aa2cac7f8fef99f8d8a7c13f9081 Mon Sep 17 00:00:00 2001 From: ACrazyTown Date: Fri, 16 Aug 2024 21:55:23 +0200 Subject: [PATCH 10/10] Fix getContextsDevice on Hashlink --- src/lime/media/openal/ALC.hx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lime/media/openal/ALC.hx b/src/lime/media/openal/ALC.hx index 62a3b6e264..4cd4379d21 100644 --- a/src/lime/media/openal/ALC.hx +++ b/src/lime/media/openal/ALC.hx @@ -76,12 +76,13 @@ class ALC public static function getContextsDevice(context:ALContext):ALDevice { - #if (lime_cffi && lime_openal && !macro) #if !hl var handle:Dynamic = NativeCFFI.lime_alc_get_contexts_device(context); + #if (lime_cffi && lime_openal && !macro) + var handle:Dynamic = NativeCFFI.lime_alc_get_contexts_device(context); if (handle != null) { return new ALDevice(handle); - } #else #end + } #end return null;