Fix web worker errors.
It took a lot of work to get web workers to work, but web workers finally work! `transferList` doesn't seem to work, though. It makes the object inaccessible as expected, but it doesn't seem to affect performance.
This commit is contained in:
11
include.xml
11
include.xml
@@ -74,8 +74,11 @@
|
|||||||
|
|
||||||
<section unless="display">
|
<section unless="display">
|
||||||
|
|
||||||
<!-- TODO: use target.threaded keyword, add html5 to the list. -->
|
<section unless="force-synchronous || force_synchronous">
|
||||||
<define name="lime-threads" if="cs || neko || cpp || java || python || hl" unless="force-synchronous || force_synchronous || emscripten" />
|
<haxedef name="lime-threads" if="neko || cpp || html5" unless="emscripten" />
|
||||||
|
<!-- `target.threaded` isn't available, so enumerate the targets instead. -->
|
||||||
|
<haxedef name="lime-threads" if="cs || java || python || hl" unless="${${haxe_ver} < 4}" />
|
||||||
|
</section>
|
||||||
|
|
||||||
<section if="cpp ${${haxe_ver} < 3.3}" unless="static_link">
|
<section if="cpp ${${haxe_ver} < 3.3}" unless="static_link">
|
||||||
<ndll name="std" haxelib="hxcpp" />
|
<ndll name="std" haxelib="hxcpp" />
|
||||||
@@ -86,11 +89,13 @@
|
|||||||
<ndll name="lime" if="native" unless="lime-console static_link || lime-switch static_link" />
|
<ndll name="lime" if="native" unless="lime-console static_link || lime-switch static_link" />
|
||||||
|
|
||||||
<dependency name="extension-api" path="dependencies/extension-api" if="android" />
|
<dependency name="extension-api" path="dependencies/extension-api" if="android" />
|
||||||
|
|
||||||
<dependency path="dependencies/howler.min.js" if="html5 howlerjs" embed="true" />
|
<dependency path="dependencies/howler.min.js" if="html5 howlerjs" embed="true" />
|
||||||
<dependency path="dependencies/pako.min.js" if="html5" embed="true" />
|
<dependency path="dependencies/pako.min.js" if="html5" embed="true" web-worker="true" />
|
||||||
<dependency path="dependencies/FileSaver.min.js" if="html5" embed="true" />
|
<dependency path="dependencies/FileSaver.min.js" if="html5" embed="true" />
|
||||||
<dependency path="dependencies/webgl-debug.js" if="html5 webgl-debug" embed="true" />
|
<dependency path="dependencies/webgl-debug.js" if="html5 webgl-debug" embed="true" />
|
||||||
<dependency path="dependencies/stats.min.js" if="html5 stats" embed="true" />
|
<dependency path="dependencies/stats.min.js" if="html5 stats" embed="true" />
|
||||||
|
|
||||||
<dependency path="dependencies/angle/d3dcompiler_47.dll" if="windows angle" unless="static_link" />
|
<dependency path="dependencies/angle/d3dcompiler_47.dll" if="windows angle" unless="static_link" />
|
||||||
<dependency path="dependencies/angle/libegl.dll" if="windows angle" unless="static_link" />
|
<dependency path="dependencies/angle/libegl.dll" if="windows angle" unless="static_link" />
|
||||||
<dependency path="dependencies/angle/libglesv2.dll" if="windows angle" unless="static_link" />
|
<dependency path="dependencies/angle/libglesv2.dll" if="windows angle" unless="static_link" />
|
||||||
|
|||||||
@@ -1,56 +1,70 @@
|
|||||||
package lime._internal.backend.html5;
|
package lime._internal.backend.html5;
|
||||||
|
|
||||||
|
import lime.app.Event;
|
||||||
|
|
||||||
#if macro
|
#if macro
|
||||||
import haxe.macro.Context;
|
import haxe.macro.Context;
|
||||||
import haxe.macro.Expr;
|
import haxe.macro.Expr;
|
||||||
import haxe.macro.Printer;
|
|
||||||
import haxe.macro.Type;
|
import haxe.macro.Type;
|
||||||
|
|
||||||
using haxe.macro.Context;
|
using haxe.macro.Context;
|
||||||
using haxe.macro.TypeTools;
|
using haxe.macro.TypeTools;
|
||||||
|
using haxe.macro.TypedExprTools;
|
||||||
#else
|
#else
|
||||||
|
// Not safe to import js package during macros.
|
||||||
import js.Browser;
|
import js.Browser;
|
||||||
|
import js.html.MessageEvent;
|
||||||
import js.html.URL;
|
import js.html.URL;
|
||||||
import js.html.Worker;
|
import js.html.Worker;
|
||||||
import js.Lib;
|
import js.Lib;
|
||||||
import js.lib.Function;
|
import js.lib.Function;
|
||||||
import js.lib.Object;
|
import js.lib.Object;
|
||||||
import js.Syntax;
|
import js.Syntax;
|
||||||
|
// Same with classes that import lots of other things.
|
||||||
import lime.app.Application;
|
import lime.app.Application;
|
||||||
import lime.app.Event;
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Emulates much of the `sys.thread.Thread` API using web workers.
|
Emulates much of the `sys.thread.Thread` API using web workers.
|
||||||
**/
|
**/
|
||||||
class HTML5Thread {
|
class HTML5Thread {
|
||||||
#if !macro
|
|
||||||
private static var __current:HTML5Thread = new HTML5Thread(Lib.global.location.href);
|
private static var __current:HTML5Thread = new HTML5Thread(Lib.global.location.href);
|
||||||
private static var __isWorker:Bool = Browser.window == null;
|
private static var __isWorker:Bool #if !macro = #if !haxe4 untyped __js__ #else Syntax.code #end ('typeof window == "undefined"') #end;
|
||||||
private static var __messages:List<Dynamic> = new List();
|
private static var __messages:List<Dynamic> = new List();
|
||||||
private static var __resolveMethods:List<Dynamic->Void> = new List();
|
private static var __resolveMethods:List<Dynamic->Void> = new List();
|
||||||
private static var __workerCount:Int = 0;
|
private static var __workerCount:Int = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The entry point into a worker script.
|
||||||
|
|
||||||
|
Lime's output JS file normally does not begin on its own. Instead it
|
||||||
|
registers a `lime.embed()` callback for index.html to use.
|
||||||
|
|
||||||
|
When this JS file is run as a web worker, it isn't running within
|
||||||
|
index.html, so `embed()` never gets called. Instead, `__init__()`
|
||||||
|
registers a message listener.
|
||||||
|
**/
|
||||||
private static function __init__():Void
|
private static function __init__():Void
|
||||||
{
|
{
|
||||||
if (__isWorker)
|
#if !macro
|
||||||
|
if (#if !haxe4 untyped __js__ #else Syntax.code #end ('typeof window == "undefined"'))
|
||||||
{
|
{
|
||||||
Lib.global.onmessage = onJobReceived;
|
Lib.global.onmessage = function(event:MessageEvent):Void
|
||||||
}
|
{
|
||||||
}
|
var job:WorkFunction<Void->Void> = event.data;
|
||||||
|
|
||||||
private static function onJobReceived(job:WorkFunction<Void->Void>):Void
|
try
|
||||||
{
|
{
|
||||||
try
|
job.dispatch();
|
||||||
{
|
Lib.global.onmessage = __current.dispatchMessage;
|
||||||
job.dispatch();
|
}
|
||||||
|
catch (e:Dynamic)
|
||||||
|
{
|
||||||
|
__current.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (e:Dynamic)
|
#end
|
||||||
{
|
|
||||||
Lib.global.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Lib.global.onmessage = __current.dispatchMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static inline function current():HTML5Thread
|
public static inline function current():HTML5Thread
|
||||||
@@ -60,18 +74,31 @@ class HTML5Thread {
|
|||||||
|
|
||||||
public static function create(job:WorkFunction<Void->Void>):HTML5Thread
|
public static function create(job:WorkFunction<Void->Void>):HTML5Thread
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
|
// Find the URL of the primary JS file.
|
||||||
var url:URL = new URL(__current.__href);
|
var url:URL = new URL(__current.__href);
|
||||||
url.pathname = Application.current.meta["file"];
|
url.pathname = url.pathname.substr(0, url.pathname.lastIndexOf("/") + 1)
|
||||||
if (url.hash.length > 0)
|
+ Application.current.meta["file"] + ".js";
|
||||||
{
|
|
||||||
url.hash += "_";
|
// Use the hash to distinguish workers.
|
||||||
}
|
if (url.hash.length > 0) url.hash += "_";
|
||||||
url.hash += __workerCount;
|
url.hash += __workerCount;
|
||||||
__workerCount++;
|
__workerCount++;
|
||||||
|
|
||||||
|
// Prepare to send the job.
|
||||||
|
job.makePortable();
|
||||||
|
|
||||||
|
// Create the worker. Because the worker's scope will not include a
|
||||||
|
// `window`, `HTML5Thread.__init__()` will add a listener.
|
||||||
var thread:HTML5Thread = new HTML5Thread(url.href, new Worker(url.href, {name: url.hash}));
|
var thread:HTML5Thread = new HTML5Thread(url.href, new Worker(url.href, {name: url.hash}));
|
||||||
|
|
||||||
|
// Send a message to the listener.
|
||||||
thread.sendMessage(job);
|
thread.sendMessage(job);
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +107,7 @@ class HTML5Thread {
|
|||||||
|
|
||||||
@param preserveClasses Whether to call `preserveClasses()` first.
|
@param preserveClasses Whether to call `preserveClasses()` first.
|
||||||
**/
|
**/
|
||||||
public static function returnMessage(message:Message, transferList:Array<Dynamic> = null, preserveClasses:Bool = true):Void
|
public static function returnMessage(message:Message, transferList:Array<Transferable> = null, preserveClasses:Bool = true):Void
|
||||||
{
|
{
|
||||||
if (__isWorker)
|
if (__isWorker)
|
||||||
{
|
{
|
||||||
@@ -109,6 +136,7 @@ class HTML5Thread {
|
|||||||
|
|
||||||
private function new(href:String, worker:Worker = null)
|
private function new(href:String, worker:Worker = null)
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
__href = href;
|
__href = href;
|
||||||
|
|
||||||
if (worker != null)
|
if (worker != null)
|
||||||
@@ -117,14 +145,13 @@ class HTML5Thread {
|
|||||||
__worker.onmessage = dispatchMessage;
|
__worker.onmessage = dispatchMessage;
|
||||||
onMessage = new Event<Dynamic->Void>();
|
onMessage = new Event<Dynamic->Void>();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// If an `HTML5Thread` instance is passed to a different thread than
|
||||||
// `HTML5Thread`'s instance functions all assume they're being
|
// where it was created, all of its instance methods will behave
|
||||||
// called on the thread where this instance was created. Therefore,
|
// incorrectly. You can still check equality, but that's it. Therefore,
|
||||||
// it isn't safe for `preserveClasses()` to actually preserve this
|
// it's best to make `preserveClasses()` skip this class.
|
||||||
// class. (But if `worker` is defined it's already a lost cause.)
|
Message.disablePreserveClasses(this);
|
||||||
Reflect.setField(this, Message.PROTOTYPE_FIELD, null);
|
#end
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,8 +160,9 @@ class HTML5Thread {
|
|||||||
|
|
||||||
@param preserveClasses Whether to call `preserveClasses()` first.
|
@param preserveClasses Whether to call `preserveClasses()` first.
|
||||||
**/
|
**/
|
||||||
public function sendMessage(message:Message, transferList:Array<Dynamic> = null, preserveClasses:Bool = true):Void
|
public function sendMessage(message:Message, transferList:Array<Transferable> = null, preserveClasses:Bool = true):Void
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
if (__worker != null)
|
if (__worker != null)
|
||||||
{
|
{
|
||||||
if (preserveClasses)
|
if (preserveClasses)
|
||||||
@@ -149,13 +177,19 @@ class HTML5Thread {
|
|||||||
// No need for `restoreClasses()` because it came from this thread.
|
// No need for `restoreClasses()` because it came from this thread.
|
||||||
__messages.add(message);
|
__messages.add(message);
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
private function dispatchMessage(message:Message):Void
|
#if !macro
|
||||||
|
private function dispatchMessage(event:MessageEvent):Void
|
||||||
{
|
{
|
||||||
|
var message:Message = event.data;
|
||||||
message.restoreClasses();
|
message.restoreClasses();
|
||||||
|
|
||||||
onMessage.dispatch(message);
|
if (onMessage != null)
|
||||||
|
{
|
||||||
|
onMessage.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
if (__resolveMethods.isEmpty())
|
if (__resolveMethods.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -166,12 +200,14 @@ class HTML5Thread {
|
|||||||
__resolveMethods.pop()(message);
|
__resolveMethods.pop()(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Closes this thread unless it's the main thread.
|
Closes this thread unless it's the main thread.
|
||||||
**/
|
**/
|
||||||
public function destroy():Void
|
public function destroy():Void
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
if (__worker != null)
|
if (__worker != null)
|
||||||
{
|
{
|
||||||
__worker.terminate();
|
__worker.terminate();
|
||||||
@@ -184,8 +220,13 @@ class HTML5Thread {
|
|||||||
}
|
}
|
||||||
catch (e:Dynamic) {}
|
catch (e:Dynamic) {}
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline function isWorker():Bool
|
||||||
|
{
|
||||||
|
return __worker != null || __isWorker;
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reads a message from the thread queue. Returns `null` if no argument is
|
Reads a message from the thread queue. Returns `null` if no argument is
|
||||||
@@ -193,6 +234,7 @@ class HTML5Thread {
|
|||||||
|
|
||||||
@param block If true, uses the `await` keyword to wait for the next
|
@param block If true, uses the `await` keyword to wait for the next
|
||||||
message. Requires the calling function to be `async`.
|
message. Requires the calling function to be `async`.
|
||||||
|
@see `lime.system.WorkOutput.JSAsync.async()`
|
||||||
**/
|
**/
|
||||||
public static macro function readMessage(block:ExprOf<Bool>):Dynamic
|
public static macro function readMessage(block:ExprOf<Bool>):Dynamic
|
||||||
{
|
{
|
||||||
@@ -226,46 +268,70 @@ abstract WorkFunction<T:haxe.Constraints.Function>(WorkFunctionData<T>) from Wor
|
|||||||
**/
|
**/
|
||||||
public var portable(get, never):Bool;
|
public var portable(get, never):Bool;
|
||||||
|
|
||||||
// `@:from` would cause errors during the macro phase. Disabling `macro`
|
#if macro
|
||||||
// during the macro phase allows other macros to call this statically.
|
/**
|
||||||
|
Parses a chain of nested `EField` expressions.
|
||||||
|
@return An array of all identifiers in the chain, as strings. If the
|
||||||
|
chain began with something other than an identifier, it will be returned
|
||||||
|
as the `initialExpr`. For instance, `array[i].foo.bar` will result in
|
||||||
|
`chain == ["foo", "bar"]` and `initialExpr == array[i]`.
|
||||||
|
**/
|
||||||
|
private static function parseFieldChain(chain:Expr):{ chain:Array<String>, ?initialExpr:Expr }
|
||||||
|
{
|
||||||
|
switch(chain.expr)
|
||||||
|
{
|
||||||
|
case EConst(CIdent(ident)):
|
||||||
|
return { chain: [ident] };
|
||||||
|
case EField(e, field):
|
||||||
|
var out = parseFieldChain(e);
|
||||||
|
out.chain.push(field);
|
||||||
|
return out;
|
||||||
|
default:
|
||||||
|
return { chain: [], initialExpr: chain };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
// `@:from` would cause errors during the macro phase.
|
||||||
@:noCompletion @:dox(hide) #if !macro @:from #end
|
@:noCompletion @:dox(hide) #if !macro @:from #end
|
||||||
public static #if !macro macro #end function fromFunction(func:ExprOf<haxe.Constraints.Function>)
|
public static #if !macro macro #end function fromFunction(func:ExprOf<haxe.Constraints.Function>)
|
||||||
{
|
{
|
||||||
#if !force_synchronous
|
var defaultOutput:Expr = macro {
|
||||||
return macro {
|
|
||||||
func: $func
|
func: $func
|
||||||
};
|
};
|
||||||
#else
|
|
||||||
trace(func);
|
|
||||||
#if haxe3
|
|
||||||
trace("haxe3");
|
|
||||||
#end
|
|
||||||
#if haxe4
|
|
||||||
trace("haxe4");
|
|
||||||
#end
|
|
||||||
#if haxe5
|
|
||||||
trace("haxe5");
|
|
||||||
#end
|
|
||||||
//trace(Context.resolveType());
|
|
||||||
var parts:Array<String> = new Printer().printExpr(func).split(".");
|
|
||||||
var functionName:String = parts.pop();
|
|
||||||
|
|
||||||
var classPath:String;
|
if (!Context.defined("lime-threads"))
|
||||||
if (parts.length > 0)
|
|
||||||
{
|
{
|
||||||
classPath = parts.join(".");
|
return defaultOutput;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var classType:ClassType = Context.getLocalClass().get();
|
// Haxe likes to pass `@:this this` instead of the actual
|
||||||
classPath = classType.pack.join(".") + "." + classType.name;
|
// expression, so use a roundabout method to convert back. As a
|
||||||
}
|
// happy side-effect, it fully qualifies the expression.
|
||||||
|
var qualifiedFunc:String = func.typeExpr().toString(true);
|
||||||
|
|
||||||
return macro {
|
// Match the package, class name, and field name.
|
||||||
classPath: $v{classPath},
|
var matcher:EReg = ~/^((?:_?\w+\.)*[A-Z]\w*)\.(_*[a-z]\w*)$/;
|
||||||
functionName: $v{functionName}
|
if (!matcher.match(qualifiedFunc))
|
||||||
};
|
{
|
||||||
#end
|
if (Context.defined("lime-warn-portability"))
|
||||||
|
{
|
||||||
|
trace(qualifiedFunc);
|
||||||
|
Context.warning("Value doesn't appear to be a static function.", func.pos);
|
||||||
|
}
|
||||||
|
return defaultOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
var classPath:String = matcher.matched(1);
|
||||||
|
var functionName:String = matcher.matched(2);
|
||||||
|
|
||||||
|
return macro {
|
||||||
|
func: $func,
|
||||||
|
classPath: $v{classPath},
|
||||||
|
functionName: $v{functionName}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -299,27 +365,6 @@ abstract WorkFunction<T:haxe.Constraints.Function>(WorkFunctionData<T>) from Wor
|
|||||||
#end
|
#end
|
||||||
return this.func;
|
return this.func;
|
||||||
}
|
}
|
||||||
else if (this.sourceCode != null)
|
|
||||||
{
|
|
||||||
var parser:EReg = ~/^function\(((?:\w+,\s*)*)\)\s*\{(.+)\s*\}$/s;
|
|
||||||
if (parser.match(this.sourceCode))
|
|
||||||
{
|
|
||||||
var paramsAndBody:Array<String> = ~/,\s*/.split(parser.matched(1));
|
|
||||||
paramsAndBody.push(parser.matched(2));
|
|
||||||
|
|
||||||
#if !macro
|
|
||||||
// Compile, binding an arbitrary `this` value. Yet another
|
|
||||||
// reason instance methods don't work.
|
|
||||||
this.func = #if haxe4 Syntax.code #else untyped __js__ #end
|
|
||||||
("Function.apply({0}, {1})", Lib.global, paramsAndBody);
|
|
||||||
#end
|
|
||||||
return this.func;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw 'Could not parse function source code: ${this.sourceCode}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw 'Object is not a valid WorkFunction: $this';
|
throw 'Object is not a valid WorkFunction: $this';
|
||||||
}
|
}
|
||||||
@@ -331,19 +376,37 @@ abstract WorkFunction<T:haxe.Constraints.Function>(WorkFunctionData<T>) from Wor
|
|||||||
**/
|
**/
|
||||||
public function makePortable(throwError:Bool = true):Bool
|
public function makePortable(throwError:Bool = true):Bool
|
||||||
{
|
{
|
||||||
if ((this.classPath == null || this.functionName == null)
|
if (this.func != null)
|
||||||
&& this.sourceCode == null && this.func != null)
|
|
||||||
{
|
{
|
||||||
#if !macro
|
// Make sure `classPath.functionName` points to the actual function.
|
||||||
this.sourceCode = (cast this.func:Function).toString();
|
if (this.classPath != null || this.functionName != null)
|
||||||
#end
|
{
|
||||||
}
|
#if !macro
|
||||||
|
var func = #if !haxe4 untyped __js__ #else Syntax.code #end
|
||||||
if (this.classPath != null && this.functionName != null
|
("$hxClasses[{0}] && $hxClasses[{0}][{1}]", this.classPath, this.functionName);
|
||||||
// The main reason instance methods don't work.
|
if (func != this.func)
|
||||||
|| this.sourceCode != null && this.sourceCode.indexOf("[native code]") < 0)
|
{
|
||||||
{
|
throw 'Could not make ${this.functionName} portable. Either ${this.functionName} isn\'t static, or ${this.classPath} is something other than a class.';
|
||||||
this.func = null;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// All set.
|
||||||
|
this.func = null;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If you aren't sure why you got this message, make sure your
|
||||||
|
// variables are of type `WorkFunction`.
|
||||||
|
// This won't work:
|
||||||
|
// var f = MyClass.staticFunction;
|
||||||
|
// bgWorker.run(f);
|
||||||
|
// ...but this will:
|
||||||
|
// var f:WorkFunction<Dynamic->Void> = MyClass.staticFunction;
|
||||||
|
// bgWorker.run(f);
|
||||||
|
throw "Only static class functions can be made portable. Set -Dlime-warn-portability to see which line caused this.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return portable;
|
return portable;
|
||||||
@@ -361,10 +424,50 @@ abstract WorkFunction<T:haxe.Constraints.Function>(WorkFunctionData<T>) from Wor
|
|||||||
@:allow(lime._internal.backend.html5.HTML5Thread)
|
@:allow(lime._internal.backend.html5.HTML5Thread)
|
||||||
abstract Message(Dynamic) from Dynamic to Dynamic
|
abstract Message(Dynamic) from Dynamic to Dynamic
|
||||||
{
|
{
|
||||||
#if !macro
|
|
||||||
private static inline var PROTOTYPE_FIELD:String = "__prototype__";
|
private static inline var PROTOTYPE_FIELD:String = "__prototype__";
|
||||||
|
private static inline var SKIP_FIELD:String = "__skipPrototype__";
|
||||||
private static inline var RESTORE_FIELD:String = "__restoreFlag__";
|
private static inline var RESTORE_FIELD:String = "__restoreFlag__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Prevents `preserveClasses()` from working on the given object.
|
||||||
|
|
||||||
|
Note: if its class isn't preserved, `cast(object, Foo)` will fail with
|
||||||
|
the unhelpful message "uncaught exception: Object" and no line number.
|
||||||
|
|
||||||
|
@param recursive Whether to apply this to the object's children as well.
|
||||||
|
**/
|
||||||
|
public static function disablePreserveClasses(object:Dynamic, recursive:Bool = false):Void
|
||||||
|
{
|
||||||
|
#if !macro
|
||||||
|
if (object == null
|
||||||
|
// Avoid looping.
|
||||||
|
|| Reflect.hasField(object, SKIP_FIELD)
|
||||||
|
// Skip primitive types.
|
||||||
|
|| !Std.isOfType(object, Object))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Reflect.setField(object, Message.SKIP_FIELD, true);
|
||||||
|
}
|
||||||
|
catch (e:Dynamic)
|
||||||
|
{
|
||||||
|
// Probably a frozen object; no need to recurse.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
for (sub in Object.values(object))
|
||||||
|
{
|
||||||
|
disablePreserveClasses(sub, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds class information to this message and all children, so that it will
|
Adds class information to this message and all children, so that it will
|
||||||
survive being passed across threads. "Children" are the values returned
|
survive being passed across threads. "Children" are the values returned
|
||||||
@@ -372,8 +475,10 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
**/
|
**/
|
||||||
public function preserveClasses():Void
|
public function preserveClasses():Void
|
||||||
{
|
{
|
||||||
// Avoid looping.
|
#if !macro
|
||||||
if (Reflect.hasField(this, PROTOTYPE_FIELD)
|
if (this == null
|
||||||
|
// Avoid looping.
|
||||||
|
|| Reflect.hasField(this, PROTOTYPE_FIELD)
|
||||||
// Skip primitive types.
|
// Skip primitive types.
|
||||||
|| !Std.isOfType(this, Object))
|
|| !Std.isOfType(this, Object))
|
||||||
{
|
{
|
||||||
@@ -381,7 +486,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preserve this object's class.
|
// Preserve this object's class.
|
||||||
if (!Std.isOfType(this, Array))
|
if (!Reflect.hasField(this, SKIP_FIELD) && !Std.isOfType(this, Array))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -399,6 +504,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
{
|
{
|
||||||
(sub:Message).preserveClasses();
|
(sub:Message).preserveClasses();
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -408,6 +514,7 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
**/
|
**/
|
||||||
private function restoreClasses(flag:Int = null):Void
|
private function restoreClasses(flag:Int = null):Void
|
||||||
{
|
{
|
||||||
|
#if !macro
|
||||||
// Attempt to choose a unique flag.
|
// Attempt to choose a unique flag.
|
||||||
if (flag == null)
|
if (flag == null)
|
||||||
{
|
{
|
||||||
@@ -428,9 +535,17 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore this object's class.
|
// Restore this object's class.
|
||||||
if (Reflect.field(this, PROTOTYPE_FIELD) != null)
|
if (!Reflect.hasField(this, SKIP_FIELD) && Reflect.field(this, PROTOTYPE_FIELD) != null)
|
||||||
{
|
{
|
||||||
Reflect.setField(this, RESTORE_FIELD, flag);
|
try
|
||||||
|
{
|
||||||
|
Reflect.setField(this, RESTORE_FIELD, flag);
|
||||||
|
}
|
||||||
|
catch (e:Dynamic)
|
||||||
|
{
|
||||||
|
// Probably a frozen object; no need to continue.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -446,13 +561,47 @@ abstract Message(Dynamic) from Dynamic to Dynamic
|
|||||||
{
|
{
|
||||||
(sub:Message).restoreClasses(flag);
|
(sub:Message).restoreClasses(flag);
|
||||||
}
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private typedef WorkFunctionData<T:haxe.Constraints.Function> = {
|
/**
|
||||||
|
Stores the class path and function name of a function, so that it can be
|
||||||
|
found again in the background thread.
|
||||||
|
**/
|
||||||
|
typedef WorkFunctionData<T:haxe.Constraints.Function> = {
|
||||||
@:optional var classPath:String;
|
@:optional var classPath:String;
|
||||||
@:optional var functionName:String;
|
@:optional var functionName:String;
|
||||||
@:optional var sourceCode:String;
|
|
||||||
@:optional var func:T;
|
@:optional var func:T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if macro
|
||||||
|
typedef Worker = Dynamic;
|
||||||
|
typedef URL = Dynamic;
|
||||||
|
class Object {}
|
||||||
|
class Browser
|
||||||
|
{
|
||||||
|
public static var window:Dynamic;
|
||||||
|
}
|
||||||
|
class Lib
|
||||||
|
{
|
||||||
|
public static var global:Dynamic = { location: {} };
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
|
/**
|
||||||
|
An object to transfer, rather than copy.
|
||||||
|
|
||||||
|
Abstract types like `lime.utils.Int32Array` and `openfl.utils.ByteArray`
|
||||||
|
can be automatically converted. However, extern classes like
|
||||||
|
`js.lib.Int32Array` typically can't.
|
||||||
|
|
||||||
|
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||||
|
**/
|
||||||
|
// Mozilla uses "transferable" and "transferrable" interchangeably, but the HTML
|
||||||
|
// specification only uses the former.
|
||||||
|
@:forward
|
||||||
|
abstract Transferable(Dynamic) #if macro from Dynamic
|
||||||
|
#else from js.lib.ArrayBuffer from js.html.MessagePort from js.html.ImageBitmap #end
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ import lime.utils.Log;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a `Future` instance which has finished with a completion value
|
Creates a `Future` instance which has finished with a completion value
|
||||||
@param error The completion value to set
|
@param value The completion value to set
|
||||||
@return A new `Future` instance
|
@return A new `Future` instance
|
||||||
**/
|
**/
|
||||||
public static function withValue<T>(value:T):Future<T>
|
public static function withValue<T>(value:T):Future<T>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package lime.app;
|
|||||||
`Future` values.
|
`Future` values.
|
||||||
|
|
||||||
While `Future` is meant to be read-only, `Promise` can be used to set the state of a future
|
While `Future` is meant to be read-only, `Promise` can be used to set the state of a future
|
||||||
for receipients of it's `Future` object. For example:
|
for recipients of it's `Future` object. For example:
|
||||||
|
|
||||||
```haxe
|
```haxe
|
||||||
function examplePromise ():Future<String> {
|
function examplePromise ():Future<String> {
|
||||||
|
|||||||
@@ -29,17 +29,11 @@ import lime.system.WorkOutput;
|
|||||||
@:noDebug
|
@:noDebug
|
||||||
#end
|
#end
|
||||||
@:forward(canceled, completed, currentThreads, activeJobs, idleThreads,
|
@:forward(canceled, completed, currentThreads, activeJobs, idleThreads,
|
||||||
onComplete, onError, onProgress, onRun, cancel)
|
minThreads, maxThreads, onComplete, onError, onProgress, onRun, cancel)
|
||||||
abstract BackgroundWorker(ThreadPool)
|
abstract BackgroundWorker(ThreadPool)
|
||||||
{
|
{
|
||||||
private static var doWorkWrapper:WorkFunction<State->WorkOutput->Void>;
|
private static var doWorkWrapper:WorkFunction<State->WorkOutput->Void>;
|
||||||
|
|
||||||
private static function doWorkImpl(state:State, output:WorkOutput):Void
|
|
||||||
{
|
|
||||||
// `dispatch()` will check if it's really a `WorkFunction`.
|
|
||||||
(state.doWork:WorkFunction<State->WorkOutput->Void>).dispatch(state.state, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
@:deprecated("Instead pass the callback to BackgroundWorker.run().")
|
@:deprecated("Instead pass the callback to BackgroundWorker.run().")
|
||||||
@:noCompletion @:dox(hide) public var doWork(get, never):{ add: (Dynamic->Void) -> Void };
|
@:noCompletion @:dox(hide) public var doWork(get, never):{ add: (Dynamic->Void) -> Void };
|
||||||
|
|
||||||
@@ -59,7 +53,7 @@ abstract BackgroundWorker(ThreadPool)
|
|||||||
{
|
{
|
||||||
if (doWorkWrapper == null)
|
if (doWorkWrapper == null)
|
||||||
{
|
{
|
||||||
doWorkWrapper = doWorkImpl;
|
doWorkWrapper = BackgroundWorkerFunctions.__doWork;
|
||||||
}
|
}
|
||||||
this = new ThreadPool(doWorkWrapper, mode, workLoad);
|
this = new ThreadPool(doWorkWrapper, mode, workLoad);
|
||||||
}
|
}
|
||||||
@@ -119,6 +113,13 @@ abstract BackgroundWorker(ThreadPool)
|
|||||||
this.__doWork = doWorkWrapper;
|
this.__doWork = doWorkWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if html5
|
||||||
|
if (this.mode == MULTI_THREADED)
|
||||||
|
{
|
||||||
|
doWork.makePortable();
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
this.queue({
|
this.queue({
|
||||||
state: state,
|
state: state,
|
||||||
doWork: doWork
|
doWork: doWork
|
||||||
@@ -127,21 +128,34 @@ abstract BackgroundWorker(ThreadPool)
|
|||||||
|
|
||||||
// Getters & Setters
|
// Getters & Setters
|
||||||
|
|
||||||
private function get_doWork():{ add: (Dynamic->Void) -> Void }
|
private function get_doWork()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
add: function(callback:Dynamic->Void)
|
add: function(callback:Dynamic->Void)
|
||||||
{
|
{
|
||||||
// Hack: overwrite `__doWork` just for this one function.
|
#if html5
|
||||||
this.__doWork = function(state:State, output:WorkOutput):Void
|
if (this.mode == MULTI_THREADED)
|
||||||
{
|
throw "Unsupported operation; instead pass the callback to BackgroundWorker.run().";
|
||||||
#if html5
|
#end
|
||||||
if (this.mode == MULTI_THREADED)
|
// Hack: overwrite `__doWork` just for this one function. Hope
|
||||||
throw "Unsupported operation; instead pass the callback to BackgroundWorker.run().";
|
// it wasn't in use!
|
||||||
#end
|
this.__doWork = #if html5 { func: #end
|
||||||
callback(state.state);
|
function(state:State, output:WorkOutput):Void
|
||||||
};
|
{
|
||||||
|
callback(state.state);
|
||||||
|
}
|
||||||
|
#if html5 } #end;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:allow(lime.system.BackgroundWorker)
|
||||||
|
private class BackgroundWorkerFunctions
|
||||||
|
{
|
||||||
|
private static function __doWork(state:State, output:WorkOutput):Void
|
||||||
|
{
|
||||||
|
// `dispatch()` will check if it's really a `WorkFunction`.
|
||||||
|
(state.doWork:WorkFunction<State->WorkOutput->Void>).dispatch(state.state, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class ThreadPool extends WorkOutput
|
|||||||
app's available time every frame. See `workIterations` for instructions
|
app's available time every frame. See `workIterations` for instructions
|
||||||
to improve the accuracy of this estimate.
|
to improve the accuracy of this estimate.
|
||||||
**/
|
**/
|
||||||
public function new(?doWork:State->WorkOutput->Void, minThreads:Int = 0, maxThreads:Int = 1, mode:ThreadMode = null, ?workLoad:Float = 1/2)
|
public function new(?doWork:WorkFunction<State->WorkOutput->Void>, minThreads:Int = 0, maxThreads:Int = 1, mode:ThreadMode = null, ?workLoad:Float = 1/2)
|
||||||
{
|
{
|
||||||
super(mode);
|
super(mode);
|
||||||
|
|
||||||
@@ -194,20 +194,20 @@ class ThreadPool extends WorkOutput
|
|||||||
|
|
||||||
Application.current.onUpdate.remove(__update);
|
Application.current.onUpdate.remove(__update);
|
||||||
|
|
||||||
#if lime_threads
|
|
||||||
for (thread in __idleThreads)
|
|
||||||
{
|
|
||||||
thread.sendMessage(new ThreadEvent(EXIT, null));
|
|
||||||
}
|
|
||||||
__idleThreads.clear();
|
|
||||||
#end
|
|
||||||
|
|
||||||
for (job in __activeJobs)
|
for (job in __activeJobs)
|
||||||
{
|
{
|
||||||
#if lime_threads
|
#if lime_threads
|
||||||
if (job.thread != null)
|
if (job.thread != null)
|
||||||
{
|
{
|
||||||
job.thread.sendMessage(new ThreadEvent(EXIT, null));
|
if (idleThreads < minThreads)
|
||||||
|
{
|
||||||
|
job.thread.sendMessage(new ThreadEvent(WORK, null));
|
||||||
|
__idleThreads.push(job.thread);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
job.thread.sendMessage(new ThreadEvent(EXIT, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@@ -299,80 +299,78 @@ class ThreadPool extends WorkOutput
|
|||||||
/**
|
/**
|
||||||
__Run this only on a background thread.__
|
__Run this only on a background thread.__
|
||||||
|
|
||||||
Retrieves pending jobs, runs them until complete, and repeats.
|
Retrieves jobs using `Thread.readMessage()`, runs them until complete,
|
||||||
|
and repeats.
|
||||||
|
|
||||||
On all targets except HTML5, the first thread message must be a
|
Before any jobs, this function requires, in order:
|
||||||
`WorkOutput` instance. Other than that, all thread messages should be
|
|
||||||
`WORK` or `EXIT` `ThreadEvent` instances.
|
1. A `WorkOutput` instance. (Omit this message in HTML5.)
|
||||||
|
2. The `doWork` function.
|
||||||
**/
|
**/
|
||||||
private static function __executeThread():Void
|
private static function __executeThread():Void
|
||||||
{
|
{
|
||||||
#if html5
|
JSAsync.async({
|
||||||
// HTML5 requires the `async` keyword, which is easiest to do inline.
|
var output:WorkOutput = #if html5 new WorkOutput(MULTI_THREADED) #else cast(Thread.readMessage(true), WorkOutput) #end;
|
||||||
#if haxe4 js.Syntax.code #else untyped __js__ #end ("(async {0})()",
|
var doWork:WorkFunction<State->WorkOutput->Void> = Thread.readMessage(true);
|
||||||
function() {
|
var job:ThreadEvent = null;
|
||||||
#end
|
|
||||||
|
|
||||||
var output:WorkOutput = #if html5 new WorkOutput(MULTI_THREADED) #else cast(Thread.readMessage(true), WorkOutput) #end;
|
while (true)
|
||||||
var doWork:WorkFunction<State->WorkOutput->Void> = Thread.readMessage(true);
|
|
||||||
var job:ThreadEvent = null;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// Get a job.
|
|
||||||
if (job == null)
|
|
||||||
{
|
{
|
||||||
job = cast(Thread.readMessage(true), ThreadEvent);
|
// Get a job.
|
||||||
|
if (job == null)
|
||||||
output.resetJobProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job.event == EXIT)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job.event != WORK || job.state == null)
|
|
||||||
{
|
|
||||||
job = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get to work.
|
|
||||||
var interruption:Dynamic = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!output.__jobComplete.value && (interruption = Thread.readMessage(false)) == null)
|
|
||||||
{
|
{
|
||||||
output.workIterations.value++;
|
do
|
||||||
doWork.dispatch(job.state, output);
|
{
|
||||||
|
job = Thread.readMessage(true);
|
||||||
|
}
|
||||||
|
while (!Std.isOfType(job, ThreadEvent));
|
||||||
|
|
||||||
|
output.resetJobProgress();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (e)
|
|
||||||
{
|
|
||||||
output.sendError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interruption == null || output.__jobComplete.value)
|
if (job.event == EXIT)
|
||||||
{
|
{
|
||||||
job = null;
|
return;
|
||||||
}
|
}
|
||||||
else if(Std.isOfType(interruption, ThreadEvent))
|
|
||||||
{
|
|
||||||
job = interruption;
|
|
||||||
output.resetJobProgress();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Ignore interruption and keep current job.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do it all again.
|
if (job.event != WORK || job.state == null)
|
||||||
}
|
{
|
||||||
|
job = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
#if html5
|
// Get to work.
|
||||||
|
var interruption:Dynamic = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!output.__jobComplete.value && (interruption = Thread.readMessage(false)) == null)
|
||||||
|
{
|
||||||
|
output.workIterations.value++;
|
||||||
|
doWork.dispatch(job.state, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
output.sendError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interruption == null || output.__jobComplete.value)
|
||||||
|
{
|
||||||
|
job = null;
|
||||||
|
}
|
||||||
|
else if(Std.isOfType(interruption, ThreadEvent))
|
||||||
|
{
|
||||||
|
job = interruption;
|
||||||
|
output.resetJobProgress();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ignore interruption and keep working.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it all again.
|
||||||
|
}
|
||||||
});
|
});
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@@ -399,7 +397,7 @@ class ThreadPool extends WorkOutput
|
|||||||
#end
|
#end
|
||||||
|
|
||||||
// Process the queue.
|
// Process the queue.
|
||||||
while (__jobQueue.length > 0 && currentThreads < maxThreads)
|
while (!__jobQueue.isEmpty() && activeJobs < maxThreads)
|
||||||
{
|
{
|
||||||
var job:ThreadEvent = __jobQueue.pop();
|
var job:ThreadEvent = __jobQueue.pop();
|
||||||
if (job.event != WORK)
|
if (job.event != WORK)
|
||||||
@@ -498,8 +496,6 @@ class ThreadPool extends WorkOutput
|
|||||||
#if lime_threads
|
#if lime_threads
|
||||||
if (mode == MULTI_THREADED)
|
if (mode == MULTI_THREADED)
|
||||||
{
|
{
|
||||||
__activeJobs.remove(threadEvent.associatedJob);
|
|
||||||
|
|
||||||
if (currentThreads > maxThreads || __jobQueue.isEmpty() && currentThreads > minThreads)
|
if (currentThreads > maxThreads || __jobQueue.isEmpty() && currentThreads > minThreads)
|
||||||
{
|
{
|
||||||
threadEvent.associatedJob.thread.sendMessage(new ThreadEvent(EXIT, null));
|
threadEvent.associatedJob.thread.sendMessage(new ThreadEvent(EXIT, null));
|
||||||
@@ -508,6 +504,8 @@ class ThreadPool extends WorkOutput
|
|||||||
{
|
{
|
||||||
__idleThreads.push(threadEvent.associatedJob.thread);
|
__idleThreads.push(threadEvent.associatedJob.thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__activeJobs.removeThread(threadEvent.associatedJob.thread);
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
|
|
||||||
@@ -517,12 +515,25 @@ class ThreadPool extends WorkOutput
|
|||||||
eventSource = null;
|
eventSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentThreads == 0)
|
if (completed)
|
||||||
{
|
{
|
||||||
Application.current.onUpdate.remove(__update);
|
Application.current.onUpdate.remove(__update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if lime_threads
|
||||||
|
private override function createThread(executeThread:WorkFunction<Void->Void>):Thread
|
||||||
|
{
|
||||||
|
var thread:Thread = super.createThread(executeThread);
|
||||||
|
#if !html5
|
||||||
|
thread.sendMessage(this);
|
||||||
|
#end
|
||||||
|
thread.sendMessage(__doWork);
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
#end
|
||||||
|
|
||||||
// Getters & Setters
|
// Getters & Setters
|
||||||
|
|
||||||
private inline function get_activeJobs():Int
|
private inline function get_activeJobs():Int
|
||||||
@@ -540,20 +551,23 @@ class ThreadPool extends WorkOutput
|
|||||||
return activeJobs + idleThreads;
|
return activeJobs + idleThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note the distinction between `doWork` and `__doWork`.
|
// Note the distinction between `doWork` and `__doWork`: the former is for
|
||||||
|
// backwards compatibility, while the latter is always used.
|
||||||
private function get_doWork():{ add: (Dynamic->Void) -> Void }
|
private function get_doWork():{ add: (Dynamic->Void) -> Void }
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
add: function(callback:Dynamic->Void)
|
add: function(callback:Dynamic->Void)
|
||||||
{
|
{
|
||||||
__doWork = function(state:State, output:WorkOutput):Void
|
#if html5
|
||||||
{
|
if (mode == MULTI_THREADED)
|
||||||
#if html5
|
throw "Unsupported operation; instead pass the callback to ThreadPool's constructor.";
|
||||||
if (mode == MULTI_THREADED)
|
#end
|
||||||
throw "Unsupported operation; instead pass the callback to ThreadPool's constructor.";
|
__doWork = #if html5 { func: #end
|
||||||
#end
|
function(state:State, output:WorkOutput):Void
|
||||||
callback(state);
|
{
|
||||||
};
|
callback(state);
|
||||||
|
}
|
||||||
|
#if html5 } #end;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ import cpp.vm.Tls;
|
|||||||
import neko.vm.Deque;
|
import neko.vm.Deque;
|
||||||
import neko.vm.Thread;
|
import neko.vm.Thread;
|
||||||
import neko.vm.Tls;
|
import neko.vm.Tls;
|
||||||
#elseif html5
|
#end
|
||||||
|
|
||||||
|
#if html5
|
||||||
import lime._internal.backend.html5.HTML5Thread as Thread;
|
import lime._internal.backend.html5.HTML5Thread as Thread;
|
||||||
|
import lime._internal.backend.html5.HTML5Thread.Transferable;
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#if macro
|
#if macro
|
||||||
@@ -75,12 +78,15 @@ class WorkOutput
|
|||||||
private var __activeJobs:ActiveJobs = new ActiveJobs();
|
private var __activeJobs:ActiveJobs = new ActiveJobs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Single-threaded mode only; the `state` provided to the active job. If
|
The `state` provided to the active job. Will only have a value during
|
||||||
available, should be included in new `ThreadEvent`s.
|
`__update()` in single-threaded mode, and will otherwise be `null`.
|
||||||
|
|
||||||
|
Include this when creating new `ThreadEvent`s.
|
||||||
**/
|
**/
|
||||||
private var __activeJob:Null<State> = null;
|
private var __activeJob:Null<State> = null;
|
||||||
|
|
||||||
private inline function new(mode:ThreadMode) {
|
private inline function new(mode:Null<ThreadMode>)
|
||||||
|
{
|
||||||
workIterations.value = 0;
|
workIterations.value = 0;
|
||||||
__jobComplete.value = false;
|
__jobComplete.value = false;
|
||||||
|
|
||||||
@@ -97,7 +103,7 @@ class WorkOutput
|
|||||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||||
**/
|
**/
|
||||||
#if (lime_threads && html5) inline #end
|
#if (lime_threads && html5) inline #end
|
||||||
public function sendComplete(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Dynamic> = null #end):Void
|
public function sendComplete(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Transferable> = null #end):Void
|
||||||
{
|
{
|
||||||
if (!__jobComplete.value)
|
if (!__jobComplete.value)
|
||||||
{
|
{
|
||||||
@@ -120,7 +126,7 @@ class WorkOutput
|
|||||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||||
**/
|
**/
|
||||||
#if (lime_threads && html5) inline #end
|
#if (lime_threads && html5) inline #end
|
||||||
public function sendError(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Dynamic> = null #end):Void
|
public function sendError(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Transferable> = null #end):Void
|
||||||
{
|
{
|
||||||
if (!__jobComplete.value)
|
if (!__jobComplete.value)
|
||||||
{
|
{
|
||||||
@@ -143,7 +149,7 @@ class WorkOutput
|
|||||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||||
**/
|
**/
|
||||||
#if (lime_threads && html5) inline #end
|
#if (lime_threads && html5) inline #end
|
||||||
public function sendProgress(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Dynamic> = null #end):Void
|
public function sendProgress(message:Dynamic = null #if (lime_threads && html5) , transferList:Array<Transferable> = null #end):Void
|
||||||
{
|
{
|
||||||
if (!__jobComplete.value)
|
if (!__jobComplete.value)
|
||||||
{
|
{
|
||||||
@@ -167,25 +173,21 @@ class WorkOutput
|
|||||||
{
|
{
|
||||||
var thread:Thread = Thread.create(executeThread);
|
var thread:Thread = Thread.create(executeThread);
|
||||||
|
|
||||||
// Platform-dependent initialization.
|
|
||||||
#if html5
|
#if html5
|
||||||
thread.onMessage.add(onMessageFromWorker.bind(thread));
|
thread.onMessage.add(onMessageFromWorker.bind(thread));
|
||||||
#else
|
|
||||||
thread.sendMessage(this);
|
|
||||||
#end
|
#end
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if html5
|
#if html5
|
||||||
private function onMessageFromWorker(thread:Thread, message:Dynamic):Void
|
private function onMessageFromWorker(thread:Thread, threadEvent:ThreadEvent):Void
|
||||||
{
|
{
|
||||||
if (!Std.isOfType(message, ThreadEvent))
|
if (threadEvent.event == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var threadEvent:ThreadEvent = message;
|
|
||||||
threadEvent.associatedJob = __activeJobs.getByThread(thread);
|
threadEvent.associatedJob = __activeJobs.getByThread(thread);
|
||||||
|
|
||||||
__jobOutput.add(threadEvent);
|
__jobOutput.add(threadEvent);
|
||||||
@@ -366,6 +368,26 @@ class ThreadEvent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class JSAsync
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
In JavaScript, runs the given block of code within an `async` function,
|
||||||
|
enabling the `await` keyword. On other targets, runs the code normally.
|
||||||
|
**/
|
||||||
|
public static macro function async(code:Expr):Expr
|
||||||
|
{
|
||||||
|
if (Context.defined("js"))
|
||||||
|
{
|
||||||
|
var jsCode:Expr = #if haxe4 macro js.Syntax.code #else macro untyped __js__ #end;
|
||||||
|
return macro $jsCode("(async {0})()", function() $code);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !(target.threaded || cpp || neko)
|
#if !(target.threaded || cpp || neko)
|
||||||
@:forward(push, add) @:forward.new
|
@:forward(push, add) @:forward.new
|
||||||
abstract Deque<T>(List<T>) from List<T> to List<T>
|
abstract Deque<T>(List<T>) from List<T> to List<T>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class Dependency
|
|||||||
// TODO: Is "forceLoad" the best name? Implement "whole-archive" on GCC
|
// TODO: Is "forceLoad" the best name? Implement "whole-archive" on GCC
|
||||||
public var embed:Bool;
|
public var embed:Bool;
|
||||||
public var forceLoad:Bool;
|
public var forceLoad:Bool;
|
||||||
|
public var webWorker:Bool;
|
||||||
public var name:String;
|
public var name:String;
|
||||||
public var path:String;
|
public var path:String;
|
||||||
|
|
||||||
|
|||||||
@@ -1752,6 +1752,11 @@ class ProjectXMLParser extends HXProject
|
|||||||
dependency.forceLoad = parseBool(element.att.resolve("force-load"));
|
dependency.forceLoad = parseBool(element.att.resolve("force-load"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.has.resolve("web-worker"))
|
||||||
|
{
|
||||||
|
dependency.webWorker = parseBool(element.att.resolve("web-worker"));
|
||||||
|
}
|
||||||
|
|
||||||
var i = dependencies.length;
|
var i = dependencies.length;
|
||||||
|
|
||||||
while (i-- > 0)
|
while (i-- > 0)
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
(function ($hx_exports, $global) { "use strict"; var $hx_script = (function (exports, global) { ::SOURCE_FILE::
|
(function ($hx_exports, $global) { "use strict"; var $hx_script = (function (exports, global) { ::SOURCE_FILE::
|
||||||
});
|
});
|
||||||
$hx_exports.lime = $hx_exports.lime || {};
|
::if false::
|
||||||
$hx_exports.lime.$scripts = $hx_exports.lime.$scripts || {};
|
If `window` is undefined, it means this script is running as a web worker.
|
||||||
$hx_exports.lime.$scripts["::APP_FILE::"] = $hx_script;
|
In that case, there's no need for exports, and all we need to do is run the
|
||||||
$hx_exports.lime.embed = function(projectName) { var exports = {};
|
static initializers.
|
||||||
var script = $hx_exports.lime.$scripts[projectName];
|
::end::if(typeof window == "undefined") {
|
||||||
if (!script) throw Error("Cannot find project name \"" + projectName + "\"");
|
$hx_script({}, $global);
|
||||||
script(exports, $global);
|
} else {
|
||||||
for (var key in exports) $hx_exports[key] = $hx_exports[key] || exports[key];
|
$hx_exports.lime = $hx_exports.lime || {};
|
||||||
var lime = exports.lime || window.lime;
|
$hx_exports.lime.$scripts = $hx_exports.lime.$scripts || {};
|
||||||
if (lime && lime.embed && this != lime.embed) lime.embed.apply(lime, arguments);
|
$hx_exports.lime.$scripts["::APP_FILE::"] = $hx_script;
|
||||||
return exports;
|
$hx_exports.lime.embed = function(projectName) { var exports = {};
|
||||||
};
|
var script = $hx_exports.lime.$scripts[projectName];
|
||||||
|
if (!script) throw Error("Cannot find project name \"" + projectName + "\"");
|
||||||
|
script(exports, $global);
|
||||||
|
for (var key in exports) $hx_exports[key] = $hx_exports[key] || exports[key];
|
||||||
|
var lime = exports.lime || window.lime;
|
||||||
|
if (lime && lime.embed && this != lime.embed) lime.embed.apply(lime, arguments);
|
||||||
|
return exports;
|
||||||
|
};
|
||||||
|
}
|
||||||
::if false::
|
::if false::
|
||||||
AMD compatibility: If define() is present we need to
|
AMD compatibility: If define() is present we need to
|
||||||
- call it, to define our module
|
- call it, to define our module
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ class HTML5Platform extends PlatformTarget
|
|||||||
if (dependency.embed && StringTools.endsWith(dependency.path, ".js") && FileSystem.exists(dependency.path))
|
if (dependency.embed && StringTools.endsWith(dependency.path, ".js") && FileSystem.exists(dependency.path))
|
||||||
{
|
{
|
||||||
var script = File.getContent(dependency.path);
|
var script = File.getContent(dependency.path);
|
||||||
|
if (!dependency.webWorker)
|
||||||
|
{
|
||||||
|
script = 'if(typeof window != "undefined") {\n' + script + "\n}";
|
||||||
|
}
|
||||||
context.embeddedLibraries.push(script);
|
context.embeddedLibraries.push(script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user