Merge branch '8.2.0-Dev' into 9.0.0-dev
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
package haxe;
|
||||
|
||||
#if !lime_cffi
|
||||
#if (!lime_cffi || macro)
|
||||
// Original haxe.Timer class
|
||||
|
||||
/*
|
||||
|
||||
@@ -945,7 +945,7 @@ class Bytes
|
||||
}
|
||||
#elseif hl
|
||||
#if !macro
|
||||
@:autoBuild(lime._internal.macros.AssetsMacro.embedBytesHL()) // Enable @:bytes embed metadata
|
||||
@:autoBuild(lime._internal.macros.AssetsMacro.embedBytes()) // Enable @:bytes embed metadata
|
||||
#end
|
||||
@:coreApi
|
||||
class Bytes
|
||||
|
||||
657
src/lime/_internal/backend/html5/HTML5Thread.hx
Normal file
657
src/lime/_internal/backend/html5/HTML5Thread.hx
Normal file
@@ -0,0 +1,657 @@
|
||||
package lime._internal.backend.html5;
|
||||
|
||||
import lime.app.Event;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
using haxe.macro.Context;
|
||||
using haxe.macro.TypeTools;
|
||||
using haxe.macro.TypedExprTools;
|
||||
#else
|
||||
// Not safe to import js package during macros.
|
||||
import js.Browser;
|
||||
import js.html.*;
|
||||
import js.Lib;
|
||||
#if haxe4
|
||||
import js.lib.Function;
|
||||
import js.lib.Object;
|
||||
import js.lib.Promise;
|
||||
import js.Syntax;
|
||||
#else
|
||||
import js.Promise;
|
||||
#end
|
||||
// Same with classes that import lots of other things.
|
||||
import lime.app.Application;
|
||||
#end
|
||||
|
||||
/**
|
||||
Emulates much of the `sys.thread.Thread` API using web workers.
|
||||
**/
|
||||
class HTML5Thread {
|
||||
private static var __current:HTML5Thread = new HTML5Thread(Lib.global.location.href);
|
||||
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 __resolveMethods:List<Dynamic->Void> = new List();
|
||||
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
|
||||
{
|
||||
#if !macro
|
||||
if (#if !haxe4 untyped __js__ #else Syntax.code #end ('typeof window == "undefined"'))
|
||||
{
|
||||
Lib.global.onmessage = function(event:MessageEvent):Void
|
||||
{
|
||||
var job:WorkFunction<Void->Void> = event.data;
|
||||
|
||||
try
|
||||
{
|
||||
Lib.global.onmessage = __current.dispatchMessage;
|
||||
job.dispatch();
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
__current.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public static inline function current():HTML5Thread
|
||||
{
|
||||
return __current;
|
||||
}
|
||||
|
||||
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);
|
||||
url.pathname = url.pathname.substr(0, url.pathname.lastIndexOf("/") + 1)
|
||||
+ Application.current.meta["file"] + ".js";
|
||||
|
||||
// Use the hash to distinguish workers.
|
||||
if (url.hash.length > 0) url.hash += "_";
|
||||
url.hash += __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));
|
||||
|
||||
// Run `job` on the new thread.
|
||||
thread.sendMessage(job);
|
||||
|
||||
return thread;
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
#if !macro
|
||||
private static inline function zeroDelay():Promise<Dynamic>
|
||||
{
|
||||
return new Promise<Dynamic>(function(resolve, _):Void
|
||||
{
|
||||
js.Lib.global.setTimeout(resolve);
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
Reads a message from the thread queue. Returns `null` if no message is
|
||||
available. This may only be called inside an `async` function.
|
||||
@param block If true, waits for the next message before returning.
|
||||
@see `lime.system.WorkOutput.JSAsync.async()`
|
||||
**/
|
||||
public static macro function readMessage(block:ExprOf<Bool>):Dynamic
|
||||
{
|
||||
var jsCode:Expr = macro #if haxe4 js.Syntax.code #else untyped __js__ #end;
|
||||
|
||||
// `onmessage` events are only received when the main function is
|
||||
// suspended, so we must insert `await` even if `block` is false.
|
||||
// TODO: find a more efficient way to read messages.
|
||||
var zeroDelayExpr:Expr = macro @:privateAccess
|
||||
$jsCode("await {0}", lime._internal.backend.html5.HTML5Thread.zeroDelay())
|
||||
.then(function(_) return lime._internal.backend.html5.HTML5Thread.__messages.pop());
|
||||
|
||||
switch (block.expr)
|
||||
{
|
||||
case EConst(CIdent("false")):
|
||||
return zeroDelayExpr;
|
||||
default:
|
||||
return macro if ($block && @:privateAccess lime._internal.backend.html5.HTML5Thread.__messages.isEmpty())
|
||||
{
|
||||
$jsCode("await {0}", new #if haxe4 js.lib.Promise #else js.Promise #end
|
||||
(function(resolve, _):Void
|
||||
{
|
||||
@:privateAccess lime._internal.backend.html5.HTML5Thread.__resolveMethods.add(resolve);
|
||||
}
|
||||
));
|
||||
}
|
||||
else
|
||||
$zeroDelayExpr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sends a message back to the thread that spawned this worker. Has no
|
||||
effect if called from the main thread.
|
||||
|
||||
@param preserveClasses Whether to call `preserveClasses()` first.
|
||||
**/
|
||||
public static function returnMessage(message:Message, transferList:Array<Transferable> = null, preserveClasses:Bool = true):Void
|
||||
{
|
||||
if (__isWorker)
|
||||
{
|
||||
if (preserveClasses)
|
||||
{
|
||||
message.preserveClasses();
|
||||
}
|
||||
|
||||
Lib.global.postMessage(message, transferList);
|
||||
}
|
||||
}
|
||||
|
||||
@:op(A == B) private static inline function equals(a:HTML5Thread, b:HTML5Thread):Bool
|
||||
{
|
||||
return a.__href == b.__href;
|
||||
}
|
||||
|
||||
/**
|
||||
Dispatches only messages coming from this `HTML5Thread`. Only available
|
||||
in the case of `HTML5Thread.create()`; never available via `current()`.
|
||||
**/
|
||||
public var onMessage(default, null):Null<Event<Dynamic->Void>>;
|
||||
|
||||
private var __href:String;
|
||||
private var __worker:Null<Worker>;
|
||||
|
||||
private function new(href:String, worker:Worker = null)
|
||||
{
|
||||
#if !macro
|
||||
__href = href;
|
||||
|
||||
if (worker != null)
|
||||
{
|
||||
__worker = worker;
|
||||
__worker.onmessage = dispatchMessage;
|
||||
onMessage = new Event<Dynamic->Void>();
|
||||
}
|
||||
|
||||
// If an `HTML5Thread` instance is passed to a different thread than
|
||||
// where it was created, all of its instance methods will behave
|
||||
// incorrectly. You can still check equality, but that's it. Therefore,
|
||||
// it's best to make `preserveClasses()` skip this class.
|
||||
Message.disablePreserveClasses(this);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Send a message to the thread queue. This message can be read using
|
||||
`readMessage()` or by listening for the `onMessage` event.
|
||||
|
||||
@param preserveClasses Whether to call `preserveClasses()` first.
|
||||
**/
|
||||
public function sendMessage(message:Message, transferList:Array<Transferable> = null, preserveClasses:Bool = true):Void
|
||||
{
|
||||
#if !macro
|
||||
if (__worker != null)
|
||||
{
|
||||
if (preserveClasses)
|
||||
{
|
||||
message.preserveClasses();
|
||||
}
|
||||
|
||||
__worker.postMessage(message, transferList);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No need for `restoreClasses()` because it came from this thread.
|
||||
__messages.add(message);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
#if !macro
|
||||
private function dispatchMessage(event:MessageEvent):Void
|
||||
{
|
||||
var message:Message = event.data;
|
||||
message.restoreClasses();
|
||||
|
||||
if (onMessage != null)
|
||||
{
|
||||
onMessage.dispatch(message);
|
||||
}
|
||||
|
||||
if (__resolveMethods.isEmpty())
|
||||
{
|
||||
__messages.add(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
__resolveMethods.pop()(message);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
Closes this thread unless it's the main thread.
|
||||
**/
|
||||
public function destroy():Void
|
||||
{
|
||||
#if !macro
|
||||
if (__worker != null)
|
||||
{
|
||||
__worker.terminate();
|
||||
}
|
||||
else if (__isWorker)
|
||||
{
|
||||
try
|
||||
{
|
||||
Lib.global.close();
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function isWorker():Bool
|
||||
{
|
||||
return __worker != null || __isWorker;
|
||||
}
|
||||
}
|
||||
|
||||
abstract WorkFunction<T:haxe.Constraints.Function>(WorkFunctionData<T>) from WorkFunctionData<T>
|
||||
{
|
||||
/**
|
||||
Whether this function is ready to copy across threads. If not, call
|
||||
`makePortable()` before sending it.
|
||||
**/
|
||||
public var portable(get, never):Bool;
|
||||
|
||||
#if macro
|
||||
/**
|
||||
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
|
||||
public static #if !macro macro #end function fromFunction(func:ExprOf<haxe.Constraints.Function>)
|
||||
{
|
||||
var defaultOutput:Expr = macro {
|
||||
func: $func
|
||||
};
|
||||
|
||||
if (!Context.defined("lime-threads"))
|
||||
{
|
||||
return defaultOutput;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Haxe likes to pass `@:this this` instead of the actual
|
||||
// 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);
|
||||
|
||||
// Match the package, class name, and field name.
|
||||
var matcher:EReg = ~/^((?:_?\w+\.)*[A-Z]\w*)\.(_*[a-z]\w*)$/;
|
||||
if (!matcher.match(qualifiedFunc))
|
||||
{
|
||||
if (Context.defined("lime-warn-portability"))
|
||||
{
|
||||
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}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Executes this function on the current thread.
|
||||
**/
|
||||
public macro function dispatch(self:ExprOf<WorkFunction<Dynamic>>, args:Array<Expr>):Expr
|
||||
{
|
||||
return macro $self.toFunction()($a{args});
|
||||
}
|
||||
|
||||
#if haxe4 @:to #end
|
||||
public function toFunction():T
|
||||
{
|
||||
if (this.func != null)
|
||||
{
|
||||
return this.func;
|
||||
}
|
||||
else if (this.classPath != null && this.functionName != null)
|
||||
{
|
||||
#if !macro
|
||||
this.func = #if !haxe4 untyped __js__ #else Syntax.code #end
|
||||
("$hxClasses[{0}][{1}]", this.classPath, this.functionName);
|
||||
#end
|
||||
return this.func;
|
||||
}
|
||||
else if (this.sourceCode != null)
|
||||
{
|
||||
#if !macro
|
||||
this.func = #if !haxe4 untyped __js__ #else Syntax.code #end
|
||||
('new Function("return " + {0})()', this.sourceCode);
|
||||
#end
|
||||
return this.func;
|
||||
}
|
||||
|
||||
throw 'Object is not a valid WorkFunction: $this';
|
||||
}
|
||||
|
||||
/**
|
||||
Attempts to make this function safe for passing across threads.
|
||||
@return Whether this function is now portable. If false, try a static
|
||||
function instead, and make sure you aren't using `bind()`.
|
||||
**/
|
||||
public function makePortable(throwError:Bool = true):Bool
|
||||
{
|
||||
if (this.func != null)
|
||||
{
|
||||
// Make sure `classPath.functionName` points to the actual function.
|
||||
if (this.classPath != null || this.functionName != null)
|
||||
{
|
||||
#if !macro
|
||||
var func = #if !haxe4 untyped __js__ #else Syntax.code #end
|
||||
("$hxClasses[{0}] && $hxClasses[{0}][{1}]", this.classPath, this.functionName);
|
||||
if (func != this.func)
|
||||
{
|
||||
throw 'Could not make ${this.functionName} portable. Either ${this.functionName} isn\'t static, or ${this.classPath} is something other than a class.';
|
||||
}
|
||||
else
|
||||
{
|
||||
// All set.
|
||||
this.func = null;
|
||||
return true;
|
||||
}
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !macro
|
||||
this.sourceCode = (cast this.func:Function).toString();
|
||||
if (this.sourceCode.indexOf("[native code]") < 0)
|
||||
{
|
||||
// All set.
|
||||
this.func = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.sourceCode = null;
|
||||
}
|
||||
#end
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Getters & Setters
|
||||
|
||||
private inline function get_portable():Bool
|
||||
{
|
||||
return this.func == null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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 functionName:String;
|
||||
@:optional var sourceCode:String;
|
||||
@:optional var func:T;
|
||||
};
|
||||
|
||||
@:forward
|
||||
@:allow(lime._internal.backend.html5.HTML5Thread)
|
||||
abstract Message(Dynamic) from Dynamic to Dynamic
|
||||
{
|
||||
private static inline var PROTOTYPE_FIELD:String = "__prototype__";
|
||||
private static inline var SKIP_FIELD:String = "__skipPrototype__";
|
||||
private static inline var RESTORE_FIELD:String = "__restoreFlag__";
|
||||
|
||||
#if !macro
|
||||
private static inline function skip(object:Dynamic):Bool
|
||||
{
|
||||
// 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)
|
||||
// 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
|
||||
// thousands or millions of fields, which can take entire seconds to
|
||||
// enumerate. This also applies to `Int8Array`, `Float64Array`, etc.
|
||||
|| object.byteLength != null && object.byteOffset != null
|
||||
&& object.buffer != null
|
||||
&& #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (object.buffer, #if haxe4 js.lib.ArrayBuffer #else js.html.ArrayBuffer #end);
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
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.
|
||||
**/
|
||||
public static function disablePreserveClasses(object:Dynamic):Void
|
||||
{
|
||||
#if !macro
|
||||
if (skip(object))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Reflect.setField(object, Message.SKIP_FIELD, true);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Adds class information to this message and all children, so that it will
|
||||
survive being passed across threads. "Children" are the values returned
|
||||
by `Object.values()`.
|
||||
**/
|
||||
public function preserveClasses():Void
|
||||
{
|
||||
#if !macro
|
||||
if (skip(this) || Reflect.hasField(this, PROTOTYPE_FIELD))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve this object's class.
|
||||
if (!#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (this, Array))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.__class__ != null)
|
||||
{
|
||||
#if haxe4
|
||||
Reflect.setField(this, PROTOTYPE_FIELD, this.__class__.__name__);
|
||||
#else
|
||||
Reflect.setField(this, PROTOTYPE_FIELD, this.__class__.__name__.join("."));
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
Reflect.setField(this, PROTOTYPE_FIELD, null);
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
// Probably a frozen object; no need to recurse.
|
||||
return;
|
||||
}
|
||||
|
||||
// While usually it's the user's job not to include any functions,
|
||||
// enums come with a built-in `toString` function that needs to be
|
||||
// removed, and it isn't fair to ask the user to know that.
|
||||
if (#if haxe4 Syntax.code #else untyped __js__ #end ('typeof {0}.toString == "function"', this))
|
||||
{
|
||||
Reflect.deleteField(this, "toString");
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse.
|
||||
for (child in Object.values(this))
|
||||
{
|
||||
(child:Message).preserveClasses();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Restores the class information preserved by `preserveClasses()`.
|
||||
|
||||
@param flag Leave this `null`.
|
||||
**/
|
||||
private function restoreClasses(flag:Int = null):Void
|
||||
{
|
||||
#if !macro
|
||||
// Attempt to choose a unique flag.
|
||||
if (flag == null)
|
||||
{
|
||||
// JavaScript's limit is 2^53; Haxe 3's limit is much lower.
|
||||
flag = Std.int(Math.random() * 0x7FFFFFFF);
|
||||
if (Reflect.field(this, RESTORE_FIELD) == flag)
|
||||
{
|
||||
flag++;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip(this) || Reflect.field(this, RESTORE_FIELD) == flag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Reflect.setField(this, RESTORE_FIELD, flag);
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
// Probably a frozen object; no need to continue.
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore this object's class.
|
||||
if (Reflect.field(this, PROTOTYPE_FIELD) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object.setPrototypeOf(this,
|
||||
#if haxe4 Syntax.code #else untyped __js__ #end
|
||||
("$hxClasses[{0}].prototype", Reflect.field(this, PROTOTYPE_FIELD)));
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
}
|
||||
|
||||
// Recurse.
|
||||
for (child in Object.values(this))
|
||||
{
|
||||
(child:Message).restoreClasses(flag);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#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 lime.utils.ArrayBuffer from js.html.MessagePort from js.html.ImageBitmap #end
|
||||
{
|
||||
}
|
||||
|
||||
#if (!haxe4 && !macro)
|
||||
@:native("Object")
|
||||
extern class Object {
|
||||
static function setPrototypeOf<T:{}>(obj:T, prototype:Null<{}>):T;
|
||||
@:pure static function values(obj:{}):Array<Dynamic>;
|
||||
static var prototype(default, never):Dynamic;
|
||||
}
|
||||
#end
|
||||
@@ -1,537 +0,0 @@
|
||||
package lime._internal.backend.kha;
|
||||
|
||||
import haxe.Timer;
|
||||
import lime.app.Application;
|
||||
import lime.app.Config;
|
||||
import lime.media.AudioManager;
|
||||
import lime.graphics.RenderContext;
|
||||
import lime.graphics.Renderer;
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.Clipboard;
|
||||
import lime.system.Display;
|
||||
import lime.system.DisplayMode;
|
||||
import lime.system.JNI;
|
||||
import lime.system.Sensor;
|
||||
import lime.system.SensorType;
|
||||
import lime.system.System;
|
||||
import lime.ui.Gamepad;
|
||||
import lime.ui.Joystick;
|
||||
import lime.ui.JoystickHatPosition;
|
||||
import lime.ui.KeyCode;
|
||||
import lime.ui.KeyModifier;
|
||||
import lime.ui.Touch;
|
||||
import lime.ui.Window;
|
||||
import openfl._internal.renderer.kha.KhaRenderer;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
@:access(haxe.Timer)
|
||||
@:access(lime.app.Application)
|
||||
@:access(lime.graphics.Renderer)
|
||||
@:access(lime.system.Clipboard)
|
||||
@:access(lime.system.Sensor)
|
||||
@:access(lime.ui.Gamepad)
|
||||
@:access(lime.ui.Joystick)
|
||||
@:access(lime.ui.Window)
|
||||
class KhaApplication
|
||||
{
|
||||
private var applicationEventInfo = new ApplicationEventInfo(UPDATE);
|
||||
private var clipboardEventInfo = new ClipboardEventInfo();
|
||||
private var currentTouches = new Map<Int, Touch>();
|
||||
private var dropEventInfo = new DropEventInfo();
|
||||
private var gamepadEventInfo = new GamepadEventInfo();
|
||||
private var joystickEventInfo = new JoystickEventInfo();
|
||||
private var keyEventInfo = new KeyEventInfo();
|
||||
private var mouseEventInfo = new MouseEventInfo();
|
||||
private var renderEventInfo = new RenderEventInfo(RENDER);
|
||||
private var sensorEventInfo = new SensorEventInfo();
|
||||
private var textEventInfo = new TextEventInfo();
|
||||
private var touchEventInfo = new TouchEventInfo();
|
||||
private var unusedTouchesPool = new List<Touch>();
|
||||
private var windowEventInfo = new WindowEventInfo();
|
||||
|
||||
public var handle:Dynamic;
|
||||
|
||||
private var frameRate:Float;
|
||||
private var parent:Application;
|
||||
private var toggleFullscreen:Bool;
|
||||
|
||||
private static function __init__() {}
|
||||
|
||||
public function new(parent:Application):Void
|
||||
{
|
||||
this.parent = parent;
|
||||
frameRate = 60;
|
||||
toggleFullscreen = true;
|
||||
}
|
||||
|
||||
public function create(config:Config):Void {}
|
||||
|
||||
public function exec():Int
|
||||
{
|
||||
#if !macro
|
||||
kha.input.Mouse.get().notify(mouseDown, mouseUp, mouseMove, mouseWheel);
|
||||
|
||||
kha.System.notifyOnRender(function(framebuffer:kha.Framebuffer)
|
||||
{
|
||||
for (renderer in parent.renderers)
|
||||
{
|
||||
KhaRenderer.framebuffer = framebuffer;
|
||||
renderer.render();
|
||||
renderer.onRender.dispatch();
|
||||
|
||||
if (!renderer.onRender.canceled)
|
||||
{
|
||||
renderer.flip();
|
||||
}
|
||||
}
|
||||
|
||||
// parent.renderer.render ();
|
||||
});
|
||||
#end
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function mouseDown(button:Int, x:Int, y:Int):Void
|
||||
{
|
||||
var window = parent.__windowByID.get(-1);
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
window.onMouseDown.dispatch(x, y, button);
|
||||
}
|
||||
}
|
||||
|
||||
private function mouseUp(button:Int, x:Int, y:Int):Void
|
||||
{
|
||||
var window = parent.__windowByID.get(-1);
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
window.onMouseUp.dispatch(x, y, button);
|
||||
}
|
||||
}
|
||||
|
||||
private function mouseMove(x:Int, y:Int, mx:Int, my:Int):Void
|
||||
{
|
||||
var window = parent.__windowByID.get(-1);
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
window.onMouseMove.dispatch(x, y);
|
||||
window.onMouseMoveRelative.dispatch(mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
private function mouseWheel(amount:Int):Void
|
||||
{
|
||||
var window = parent.__windowByID.get(-1);
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
window.onMouseWheel.dispatch(0, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public function exit():Void {}
|
||||
|
||||
public function getFrameRate():Float
|
||||
{
|
||||
return frameRate;
|
||||
}
|
||||
|
||||
private function handleApplicationEvent():Void {}
|
||||
|
||||
private function handleClipboardEvent():Void {}
|
||||
|
||||
private function handleDropEvent():Void {}
|
||||
|
||||
private function handleGamepadEvent():Void {}
|
||||
|
||||
private function handleJoystickEvent():Void {}
|
||||
|
||||
private function handleKeyEvent():Void {}
|
||||
|
||||
private function handleMouseEvent():Void {}
|
||||
|
||||
private function handleRenderEvent():Void {}
|
||||
|
||||
private function handleSensorEvent():Void {}
|
||||
|
||||
private function handleTextEvent():Void {}
|
||||
|
||||
private function handleTouchEvent():Void {}
|
||||
|
||||
private function handleWindowEvent():Void {}
|
||||
|
||||
public function setFrameRate(value:Float):Float
|
||||
{
|
||||
return frameRate = value;
|
||||
}
|
||||
|
||||
private function updateTimer():Void {}
|
||||
}
|
||||
|
||||
private class ApplicationEventInfo
|
||||
{
|
||||
public var deltaTime:Int;
|
||||
public var type:ApplicationEventType;
|
||||
|
||||
public function new(type:ApplicationEventType = null, deltaTime:Int = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.deltaTime = deltaTime;
|
||||
}
|
||||
|
||||
public function clone():ApplicationEventInfo
|
||||
{
|
||||
return new ApplicationEventInfo(type, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract ApplicationEventType(Int)
|
||||
{
|
||||
var UPDATE = 0;
|
||||
var EXIT = 1;
|
||||
}
|
||||
|
||||
private class ClipboardEventInfo
|
||||
{
|
||||
public var type:ClipboardEventType;
|
||||
|
||||
public function new(type:ClipboardEventType = null)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public function clone():ClipboardEventInfo
|
||||
{
|
||||
return new ClipboardEventInfo(type);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract ClipboardEventType(Int)
|
||||
{
|
||||
var UPDATE = 0;
|
||||
}
|
||||
|
||||
private class DropEventInfo
|
||||
{
|
||||
#if hl
|
||||
public var file:hl.Bytes;
|
||||
#else
|
||||
public var file:String;
|
||||
#end
|
||||
public var type:DropEventType;
|
||||
|
||||
public function new(type:DropEventType = null, file:String = null)
|
||||
{
|
||||
this.type = type;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public function clone():DropEventInfo
|
||||
{
|
||||
return new DropEventInfo(type, file);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract DropEventType(Int)
|
||||
{
|
||||
var DROP_FILE = 0;
|
||||
}
|
||||
|
||||
private class GamepadEventInfo
|
||||
{
|
||||
public var axis:Int;
|
||||
public var button:Int;
|
||||
public var id:Int;
|
||||
public var type:GamepadEventType;
|
||||
public var value:Float;
|
||||
|
||||
public function new(type:GamepadEventType = null, id:Int = 0, button:Int = 0, axis:Int = 0, value:Float = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.button = button;
|
||||
this.axis = axis;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public function clone():GamepadEventInfo
|
||||
{
|
||||
return new GamepadEventInfo(type, id, button, axis, value);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract GamepadEventType(Int)
|
||||
{
|
||||
var AXIS_MOVE = 0;
|
||||
var BUTTON_DOWN = 1;
|
||||
var BUTTON_UP = 2;
|
||||
var CONNECT = 3;
|
||||
var DISCONNECT = 4;
|
||||
}
|
||||
|
||||
private class JoystickEventInfo
|
||||
{
|
||||
public var id:Int;
|
||||
public var index:Int;
|
||||
public var type:JoystickEventType;
|
||||
public var value:Int;
|
||||
public var x:Float;
|
||||
public var y:Float;
|
||||
|
||||
public function new(type:JoystickEventType = null, id:Int = 0, index:Int = 0, value:Int = 0, x:Float = 0, y:Float = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public function clone():JoystickEventInfo
|
||||
{
|
||||
return new JoystickEventInfo(type, id, index, value, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract JoystickEventType(Int)
|
||||
{
|
||||
var AXIS_MOVE = 0;
|
||||
var HAT_MOVE = 1;
|
||||
var TRACKBALL_MOVE = 2;
|
||||
var BUTTON_DOWN = 3;
|
||||
var BUTTON_UP = 4;
|
||||
var CONNECT = 5;
|
||||
var DISCONNECT = 6;
|
||||
}
|
||||
|
||||
private class KeyEventInfo
|
||||
{
|
||||
public var keyCode:Int;
|
||||
public var modifier:Int;
|
||||
public var type:KeyEventType;
|
||||
public var windowID:Int;
|
||||
|
||||
public function new(type:KeyEventType = null, windowID:Int = 0, keyCode:Int = 0, modifier:Int = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.windowID = windowID;
|
||||
this.keyCode = keyCode;
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
public function clone():KeyEventInfo
|
||||
{
|
||||
return new KeyEventInfo(type, windowID, keyCode, modifier);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract KeyEventType(Int)
|
||||
{
|
||||
var KEY_DOWN = 0;
|
||||
var KEY_UP = 1;
|
||||
}
|
||||
|
||||
private class MouseEventInfo
|
||||
{
|
||||
public var button:Int;
|
||||
public var movementX:Float;
|
||||
public var movementY:Float;
|
||||
public var type:MouseEventType;
|
||||
public var windowID:Int;
|
||||
public var x:Float;
|
||||
public var y:Float;
|
||||
|
||||
public function new(type:MouseEventType = null, windowID:Int = 0, x:Float = 0, y:Float = 0, button:Int = 0, movementX:Float = 0, movementY:Float = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.windowID = 0;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.button = button;
|
||||
this.movementX = movementX;
|
||||
this.movementY = movementY;
|
||||
}
|
||||
|
||||
public function clone():MouseEventInfo
|
||||
{
|
||||
return new MouseEventInfo(type, windowID, x, y, button, movementX, movementY);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract MouseEventType(Int)
|
||||
{
|
||||
var MOUSE_DOWN = 0;
|
||||
var MOUSE_UP = 1;
|
||||
var MOUSE_MOVE = 2;
|
||||
var MOUSE_WHEEL = 3;
|
||||
}
|
||||
|
||||
private class RenderEventInfo
|
||||
{
|
||||
public var context:RenderContext;
|
||||
public var type:RenderEventType;
|
||||
|
||||
public function new(type:RenderEventType = null, context:RenderContext = null)
|
||||
{
|
||||
this.type = type;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public function clone():RenderEventInfo
|
||||
{
|
||||
return new RenderEventInfo(type, context);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract RenderEventType(Int)
|
||||
{
|
||||
var RENDER = 0;
|
||||
var RENDER_CONTEXT_LOST = 1;
|
||||
var RENDER_CONTEXT_RESTORED = 2;
|
||||
}
|
||||
|
||||
private class SensorEventInfo
|
||||
{
|
||||
public var id:Int;
|
||||
public var x:Float;
|
||||
public var y:Float;
|
||||
public var z:Float;
|
||||
public var type:SensorEventType;
|
||||
|
||||
public function new(type:SensorEventType = null, id:Int = 0, x:Float = 0, y:Float = 0, z:Float = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public function clone():SensorEventInfo
|
||||
{
|
||||
return new SensorEventInfo(type, id, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract SensorEventType(Int)
|
||||
{
|
||||
var ACCELEROMETER = 0;
|
||||
}
|
||||
|
||||
private class TextEventInfo
|
||||
{
|
||||
public var id:Int;
|
||||
public var length:Int;
|
||||
public var start:Int;
|
||||
#if hl
|
||||
public var text:hl.Bytes;
|
||||
#else
|
||||
public var text:String;
|
||||
#end
|
||||
public var type:TextEventType;
|
||||
public var windowID:Int;
|
||||
|
||||
public function new(type:TextEventType = null, windowID:Int = 0, text:String = "", start:Int = 0, length:Int = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.windowID = windowID;
|
||||
this.text = text;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public function clone():TextEventInfo
|
||||
{
|
||||
return new TextEventInfo(type, windowID, text, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract TextEventType(Int)
|
||||
{
|
||||
var TEXT_INPUT = 0;
|
||||
var TEXT_EDIT = 1;
|
||||
}
|
||||
|
||||
private class TouchEventInfo
|
||||
{
|
||||
public var device:Int;
|
||||
public var dx:Float;
|
||||
public var dy:Float;
|
||||
public var id:Int;
|
||||
public var pressure:Float;
|
||||
public var type:TouchEventType;
|
||||
public var x:Float;
|
||||
public var y:Float;
|
||||
|
||||
public function new(type:TouchEventType = null, x:Float = 0, y:Float = 0, id:Int = 0, dx:Float = 0, dy:Float = 0, pressure:Float = 0, device:Int = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.id = id;
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
this.pressure = pressure;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public function clone():TouchEventInfo
|
||||
{
|
||||
return new TouchEventInfo(type, x, y, id, dx, dy, pressure, device);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract TouchEventType(Int)
|
||||
{
|
||||
var TOUCH_START = 0;
|
||||
var TOUCH_END = 1;
|
||||
var TOUCH_MOVE = 2;
|
||||
}
|
||||
|
||||
private class WindowEventInfo
|
||||
{
|
||||
public var height:Int;
|
||||
public var type:WindowEventType;
|
||||
public var width:Int;
|
||||
public var windowID:Int;
|
||||
public var x:Int;
|
||||
public var y:Int;
|
||||
|
||||
public function new(type:WindowEventType = null, windowID:Int = 0, width:Int = 0, height:Int = 0, x:Int = 0, y:Int = 0)
|
||||
{
|
||||
this.type = type;
|
||||
this.windowID = windowID;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public function clone():WindowEventInfo
|
||||
{
|
||||
return new WindowEventInfo(type, windowID, width, height, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
#if (haxe_ver >= 4.0) private enum #else @:enum private #end abstract WindowEventType(Int)
|
||||
{
|
||||
var WINDOW_ACTIVATE = 0;
|
||||
var WINDOW_CLOSE = 1;
|
||||
var WINDOW_DEACTIVATE = 2;
|
||||
var WINDOW_ENTER = 3;
|
||||
var WINDOW_FOCUS_IN = 4;
|
||||
var WINDOW_FOCUS_OUT = 5;
|
||||
var WINDOW_LEAVE = 6;
|
||||
var WINDOW_MINIMIZE = 7;
|
||||
var WINDOW_MOVE = 8;
|
||||
var WINDOW_RESIZE = 9;
|
||||
var WINDOW_RESTORE = 10;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import lime.graphics.OpenGLRenderContext;
|
||||
import lime.graphics.RenderContext;
|
||||
import lime.math.Rectangle;
|
||||
import lime.media.AudioManager;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.Clipboard;
|
||||
import lime.system.Display;
|
||||
import lime.system.DisplayMode;
|
||||
@@ -89,7 +90,7 @@ class NativeApplication
|
||||
|
||||
private function advanceTimer():Void
|
||||
{
|
||||
#if lime_cffi
|
||||
#if (lime_cffi && !macro)
|
||||
if (pauseTimer > -1)
|
||||
{
|
||||
var offset = System.getTimer() - pauseTimer;
|
||||
@@ -187,7 +188,7 @@ class NativeApplication
|
||||
{
|
||||
for (window in parent.windows)
|
||||
{
|
||||
window.onDropFile.dispatch(#if hl @:privateAccess String.fromUTF8(dropEventInfo.file) #else dropEventInfo.file #end);
|
||||
window.onDropFile.dispatch(CFFI.stringValue(dropEventInfo.file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,10 +228,6 @@ class NativeApplication
|
||||
var joystick = Joystick.devices.get(joystickEventInfo.id);
|
||||
if (joystick != null) joystick.onHatMove.dispatch(joystickEventInfo.index, joystickEventInfo.eventValue);
|
||||
|
||||
case TRACKBALL_MOVE:
|
||||
var joystick = Joystick.devices.get(joystickEventInfo.id);
|
||||
if (joystick != null) joystick.onTrackballMove.dispatch(joystickEventInfo.index, joystickEventInfo.x, joystickEventInfo.y);
|
||||
|
||||
case BUTTON_DOWN:
|
||||
var joystick = Joystick.devices.get(joystickEventInfo.id);
|
||||
if (joystick != null) joystick.onButtonDown.dispatch(joystickEventInfo.index);
|
||||
@@ -289,7 +286,7 @@ class NativeApplication
|
||||
}
|
||||
|
||||
#if rpi
|
||||
if (keyCode == ESCAPE && modifier == KeyModifier.NONE && type == KEY_UP && !window.onKeyUp.canceled)
|
||||
if (keyCode == ESCAPE && modifier.ctrlKey && type == KEY_DOWN)
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
@@ -430,10 +427,10 @@ class NativeApplication
|
||||
switch (textEventInfo.type)
|
||||
{
|
||||
case TEXT_INPUT:
|
||||
window.onTextInput.dispatch(#if hl @:privateAccess String.fromUTF8(textEventInfo.text) #else textEventInfo.text #end);
|
||||
window.onTextInput.dispatch(CFFI.stringValue(textEventInfo.text));
|
||||
|
||||
case TEXT_EDIT:
|
||||
window.onTextEdit.dispatch(#if hl @:privateAccess String.fromUTF8(textEventInfo.text) #else textEventInfo.text #end, textEventInfo.start,
|
||||
window.onTextEdit.dispatch(CFFI.stringValue(textEventInfo.text), textEventInfo.start,
|
||||
textEventInfo.length);
|
||||
|
||||
default:
|
||||
@@ -577,7 +574,7 @@ class NativeApplication
|
||||
|
||||
private function updateTimer():Void
|
||||
{
|
||||
#if lime_cffi
|
||||
#if (lime_cffi && !macro)
|
||||
if (Timer.sRunningTimers.length > 0)
|
||||
{
|
||||
var currentTime = System.getTimer();
|
||||
@@ -752,7 +749,6 @@ class NativeApplication
|
||||
{
|
||||
var AXIS_MOVE = 0;
|
||||
var HAT_MOVE = 1;
|
||||
var TRACKBALL_MOVE = 2;
|
||||
var BUTTON_DOWN = 3;
|
||||
var BUTTON_UP = 4;
|
||||
var CONNECT = 5;
|
||||
|
||||
@@ -213,8 +213,6 @@ class NativeCFFI
|
||||
|
||||
@:cffi private static function lime_joystick_get_num_hats(id:Int):Int;
|
||||
|
||||
@:cffi private static function lime_joystick_get_num_trackballs(id:Int):Int;
|
||||
|
||||
@:cffi private static function lime_joystick_event_manager_register(callback:Dynamic, eventObject:Dynamic):Void;
|
||||
|
||||
@:cffi private static function lime_jpeg_decode_bytes(data:Dynamic, decodeData:Bool, buffer:Dynamic):Dynamic;
|
||||
@@ -495,8 +493,6 @@ class NativeCFFI
|
||||
private static var lime_joystick_get_num_axes = new cpp.Callable<Int->Int>(cpp.Prime._loadPrime("lime", "lime_joystick_get_num_axes", "ii", false));
|
||||
private static var lime_joystick_get_num_buttons = new cpp.Callable<Int->Int>(cpp.Prime._loadPrime("lime", "lime_joystick_get_num_buttons", "ii", false));
|
||||
private static var lime_joystick_get_num_hats = new cpp.Callable<Int->Int>(cpp.Prime._loadPrime("lime", "lime_joystick_get_num_hats", "ii", false));
|
||||
private static var lime_joystick_get_num_trackballs = new cpp.Callable<Int->Int>(cpp.Prime._loadPrime("lime", "lime_joystick_get_num_trackballs", "ii",
|
||||
false));
|
||||
private static var lime_joystick_event_manager_register = new cpp.Callable<cpp.Object->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime",
|
||||
"lime_joystick_event_manager_register", "oov", false));
|
||||
private static var lime_jpeg_decode_bytes = new cpp.Callable<cpp.Object->Bool->cpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime",
|
||||
@@ -706,7 +702,6 @@ class NativeCFFI
|
||||
private static var lime_joystick_get_num_axes = CFFI.load("lime", "lime_joystick_get_num_axes", 1);
|
||||
private static var lime_joystick_get_num_buttons = CFFI.load("lime", "lime_joystick_get_num_buttons", 1);
|
||||
private static var lime_joystick_get_num_hats = CFFI.load("lime", "lime_joystick_get_num_hats", 1);
|
||||
private static var lime_joystick_get_num_trackballs = CFFI.load("lime", "lime_joystick_get_num_trackballs", 1);
|
||||
private static var lime_joystick_event_manager_register = CFFI.load("lime", "lime_joystick_event_manager_register", 2);
|
||||
private static var lime_jpeg_decode_bytes = CFFI.load("lime", "lime_jpeg_decode_bytes", 3);
|
||||
private static var lime_jpeg_decode_file = CFFI.load("lime", "lime_jpeg_decode_file", 3);
|
||||
@@ -1113,11 +1108,6 @@ class NativeCFFI
|
||||
return 0;
|
||||
}
|
||||
|
||||
@:hlNative("lime", "hl_joystick_get_num_trackballs") private static function lime_joystick_get_num_trackballs(id:Int):Int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@:hlNative("lime", "hl_joystick_event_manager_register") private static function lime_joystick_event_manager_register(callback:Void->Void,
|
||||
eventObject:JoystickEventInfo):Void {}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import lime.net.HTTPRequest;
|
||||
import lime.net.HTTPRequestHeader;
|
||||
import lime.net.HTTPRequestMethod;
|
||||
import lime.system.ThreadPool;
|
||||
import lime.system.WorkOutput;
|
||||
#if sys
|
||||
#if haxe4
|
||||
import sys.thread.Deque;
|
||||
@@ -282,13 +283,12 @@ class NativeHTTPRequest
|
||||
if (localThreadPool == null)
|
||||
{
|
||||
localThreadPool = new ThreadPool(0, 1);
|
||||
localThreadPool.doWork.add(localThreadPool_doWork);
|
||||
localThreadPool.onProgress.add(localThreadPool_onProgress);
|
||||
localThreadPool.onComplete.add(localThreadPool_onComplete);
|
||||
localThreadPool.onError.add(localThreadPool_onError);
|
||||
}
|
||||
|
||||
localThreadPool.queue({instance: this, uri: uri});
|
||||
localThreadPool.run(localThreadPool_doWork, {instance: this, uri: uri});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -316,7 +316,6 @@ class NativeHTTPRequest
|
||||
if (multiThreadPool == null)
|
||||
{
|
||||
multiThreadPool = new ThreadPool(0, 1);
|
||||
multiThreadPool.doWork.add(multiThreadPool_doWork);
|
||||
multiThreadPool.onProgress.add(multiThreadPool_onProgress);
|
||||
multiThreadPool.onComplete.add(multiThreadPool_onComplete);
|
||||
}
|
||||
@@ -324,7 +323,7 @@ class NativeHTTPRequest
|
||||
if (!multiThreadPoolRunning)
|
||||
{
|
||||
multiThreadPoolRunning = true;
|
||||
multiThreadPool.queue();
|
||||
multiThreadPool.run(multiThreadPool_doWork, multi);
|
||||
}
|
||||
|
||||
if (multiProgressTimer == null)
|
||||
@@ -378,7 +377,7 @@ class NativeHTTPRequest
|
||||
}
|
||||
}
|
||||
|
||||
private function curl_onProgress(curl:CURL, dltotal:Float, dlnow:Float, uptotal:Float, upnow:Float):Void
|
||||
private function curl_onProgress(curl:CURL, dltotal:Float, dlnow:Float, uptotal:Float, upnow:Float):Int
|
||||
{
|
||||
if (upnow > writeBytesLoaded || dlnow > writeBytesLoaded || uptotal > writeBytesTotal || dltotal > writeBytesTotal)
|
||||
{
|
||||
@@ -390,6 +389,8 @@ class NativeHTTPRequest
|
||||
// Wrong thread
|
||||
// promise.progress (bytesLoaded, bytesTotal);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function curl_onWrite(curl:CURL, output:Bytes):Int
|
||||
@@ -399,7 +400,7 @@ class NativeHTTPRequest
|
||||
return output.length;
|
||||
}
|
||||
|
||||
private static function localThreadPool_doWork(state:Dynamic):Void
|
||||
private static function localThreadPool_doWork(state:Dynamic, output:WorkOutput):Void
|
||||
{
|
||||
var instance:NativeHTTPRequest = state.instance;
|
||||
var path:String = state.uri;
|
||||
@@ -420,7 +421,7 @@ class NativeHTTPRequest
|
||||
|
||||
if (path == null #if (sys && !android) || !FileSystem.exists(path) #end)
|
||||
{
|
||||
localThreadPool.sendError({instance: instance, promise: instance.promise, error: "Cannot load file: " + path});
|
||||
output.sendError({instance: instance, promise: instance.promise, error: "Cannot load file: " + path});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -428,18 +429,18 @@ class NativeHTTPRequest
|
||||
|
||||
if (instance.bytes != null)
|
||||
{
|
||||
localThreadPool.sendProgress(
|
||||
output.sendProgress(
|
||||
{
|
||||
instance: instance,
|
||||
promise: instance.promise,
|
||||
bytesLoaded: instance.bytes.length,
|
||||
bytesTotal: instance.bytes.length
|
||||
});
|
||||
localThreadPool.sendComplete({instance: instance, promise: instance.promise, result: instance.bytes});
|
||||
output.sendComplete({instance: instance, promise: instance.promise, result: instance.bytes});
|
||||
}
|
||||
else
|
||||
{
|
||||
localThreadPool.sendError({instance: instance, promise: instance.promise, error: "Cannot load file: " + path});
|
||||
output.sendError({instance: instance, promise: instance.promise, error: "Cannot load file: " + path});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,7 +493,7 @@ class NativeHTTPRequest
|
||||
promise.progress(state.bytesLoaded, state.bytesTotal);
|
||||
}
|
||||
|
||||
private static function multiThreadPool_doWork(_):Void
|
||||
private static function multiThreadPool_doWork(multi:CURLMulti, output:WorkOutput):Void
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@@ -510,7 +511,7 @@ class NativeHTTPRequest
|
||||
|
||||
if (message == null && multi.runningHandles == 0)
|
||||
{
|
||||
multiThreadPool.sendComplete();
|
||||
output.sendComplete();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -525,7 +526,7 @@ class NativeHTTPRequest
|
||||
multi.removeHandle(curl);
|
||||
curl.cleanup();
|
||||
|
||||
multiThreadPool.sendProgress({curl: curl, result: message.result, status: status});
|
||||
output.sendProgress({curl: curl, result: message.result, status: status});
|
||||
message = multi.infoRead();
|
||||
}
|
||||
}
|
||||
@@ -540,7 +541,7 @@ class NativeHTTPRequest
|
||||
if (curl != null)
|
||||
{
|
||||
multiAddHandle.push(curl);
|
||||
multiThreadPool.queue();
|
||||
multiThreadPool.run(multiThreadPool_doWork, multi);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@ import lime.graphics.opengl.GLTexture;
|
||||
import lime.graphics.opengl.GLUniformLocation;
|
||||
import lime.graphics.opengl.GL;
|
||||
import lime.graphics.RenderContextType;
|
||||
import lime.system.CFFI;
|
||||
import lime.utils.DataPointer;
|
||||
import lime.utils.Float32Array;
|
||||
import lime.utils.Int32Array;
|
||||
@@ -1395,7 +1396,7 @@ class NativeOpenGLRenderContext
|
||||
return {
|
||||
size: result.size,
|
||||
type: result.type,
|
||||
name: @:privateAccess String.fromUTF8(result.name)
|
||||
name: CFFI.stringValue(result.name)
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -1420,7 +1421,7 @@ class NativeOpenGLRenderContext
|
||||
return {
|
||||
size: result.size,
|
||||
type: result.type,
|
||||
name: @:privateAccess String.fromUTF8(result.name)
|
||||
name: CFFI.stringValue(result.name)
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -1455,10 +1456,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_active_uniform_block_name(__getObjectID(program), uniformBlockIndex);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -1901,10 +1899,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_program_info_log(__getObjectID(program));
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -2039,10 +2034,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_shader_info_log(__getObjectID(shader));
|
||||
#if hl
|
||||
var result = (result != null) ? @:privateAccess String.fromUTF8(result) : null;
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -2066,10 +2058,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_shader_source(__getObjectID(shader));
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -2079,10 +2068,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_string(name);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -2092,10 +2078,7 @@ class NativeOpenGLRenderContext
|
||||
{
|
||||
#if (lime_cffi && (lime_opengl || lime_opengles) && !macro)
|
||||
var result = NativeCFFI.lime_gl_get_stringi(name, index);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -2207,7 +2190,7 @@ class NativeOpenGLRenderContext
|
||||
return {
|
||||
size: result.size,
|
||||
type: result.type,
|
||||
name: @:privateAccess String.fromUTF8(result.name)
|
||||
name: CFFI.stringValue(result.name)
|
||||
};
|
||||
}
|
||||
else
|
||||
|
||||
@@ -15,6 +15,7 @@ import lime.graphics.OpenGLRenderContext;
|
||||
import lime.graphics.RenderContext;
|
||||
import lime.math.Rectangle;
|
||||
import lime.math.Vector2;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.Display;
|
||||
import lime.system.DisplayMode;
|
||||
import lime.system.JNI;
|
||||
@@ -123,11 +124,7 @@ class NativeWindow
|
||||
var context = new RenderContext();
|
||||
context.window = parent;
|
||||
|
||||
#if hl
|
||||
var contextType = @:privateAccess String.fromUTF8(NativeCFFI.lime_window_get_context_type(handle));
|
||||
#else
|
||||
var contextType:String = NativeCFFI.lime_window_get_context_type(handle);
|
||||
#end
|
||||
var contextType:String = CFFI.stringValue(NativeCFFI.lime_window_get_context_type(handle));
|
||||
|
||||
switch (contextType)
|
||||
{
|
||||
@@ -176,6 +173,13 @@ class NativeWindow
|
||||
|
||||
setFrameRate(Reflect.hasField(attributes, "frameRate") ? attributes.frameRate : 60);
|
||||
#end
|
||||
|
||||
// SDL 2 enables text input events by default, but we want them only
|
||||
// when requested. otherwise, we might get weird behavior like IME
|
||||
// candidate windows appearing unexpectedly when holding down a key.
|
||||
// See, for example: openfl/openfl#2697
|
||||
// it appears that SDL 3 may behave differently, if we ever upgrade.
|
||||
setTextInputEnabled(false);
|
||||
}
|
||||
|
||||
public function alert(message:String, title:String):Void
|
||||
|
||||
@@ -15,8 +15,11 @@ import sys.FileSystem;
|
||||
|
||||
class AssetsMacro
|
||||
{
|
||||
#if !macro
|
||||
macro public static function cacheVersion() {}
|
||||
#if (!macro || display)
|
||||
macro public static function cacheVersion()
|
||||
{
|
||||
return macro 0;
|
||||
}
|
||||
#else
|
||||
macro public static function cacheVersion()
|
||||
{
|
||||
@@ -26,77 +29,22 @@ class AssetsMacro
|
||||
macro public static function embedBytes():Array<Field>
|
||||
{
|
||||
var fields = embedData(":file");
|
||||
if (fields == null) return null;
|
||||
|
||||
if (fields != null)
|
||||
var superCall = Context.defined("html5") ? macro super(bytes.b.buffer)
|
||||
: Context.defined("hl") ? macro super(bytes.b, bytes.length)
|
||||
: macro super(bytes.length, bytes.b);
|
||||
|
||||
var definition = macro class Temp
|
||||
{
|
||||
#if !display
|
||||
var constructor = macro
|
||||
{
|
||||
var bytes = haxe.Resource.getBytes(resourceName);
|
||||
#if html5
|
||||
super(bytes.b.buffer);
|
||||
#elseif hl
|
||||
super(bytes.b, bytes.length);
|
||||
#else
|
||||
super(bytes.length, bytes.b);
|
||||
#end
|
||||
};
|
||||
public function new(?length:Int, ?bytesData:haxe.io.BytesData)
|
||||
{
|
||||
var bytes = haxe.Resource.getBytes(resourceName);
|
||||
$superCall;
|
||||
}
|
||||
};
|
||||
|
||||
var args = [
|
||||
{name: "length", opt: true, type: macro:Int},
|
||||
{name: "bytesData", opt: true, type: macro:haxe.io.BytesData}
|
||||
];
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: args,
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
macro public static function embedBytesHL():Array<Field>
|
||||
{
|
||||
var fields = embedData(":file");
|
||||
|
||||
if (fields != null)
|
||||
{
|
||||
#if !display
|
||||
var constructor = macro
|
||||
{
|
||||
var bytes = haxe.Resource.getBytes(resourceName);
|
||||
super(bytes.b, bytes.length);
|
||||
};
|
||||
|
||||
var args = [
|
||||
{name: "length", opt: true, type: macro:Int},
|
||||
{name: "bytesData", opt: true, type: macro:haxe.io.BytesData}
|
||||
];
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: args,
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
}
|
||||
fields.push(definition.fields[0]);
|
||||
|
||||
return fields;
|
||||
}
|
||||
@@ -104,142 +52,115 @@ class AssetsMacro
|
||||
macro public static function embedByteArray():Array<Field>
|
||||
{
|
||||
var fields = embedData(":file");
|
||||
if (fields == null) return null;
|
||||
|
||||
if (fields != null)
|
||||
var definition = macro class Temp
|
||||
{
|
||||
#if !display
|
||||
var constructor = macro
|
||||
{
|
||||
super();
|
||||
public function new(?length:Int = 0)
|
||||
{
|
||||
super();
|
||||
|
||||
var bytes = haxe.Resource.getBytes(resourceName);
|
||||
__fromBytes(bytes);
|
||||
};
|
||||
var bytes = haxe.Resource.getBytes(resourceName);
|
||||
__fromBytes(bytes);
|
||||
}
|
||||
};
|
||||
|
||||
var args = [
|
||||
{
|
||||
name: "length",
|
||||
opt: true,
|
||||
type: macro:Int,
|
||||
value: macro 0
|
||||
}
|
||||
];
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: args,
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
}
|
||||
fields.push(definition.fields[0]);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static function embedData(metaName:String, encode:Bool = false):Array<Field>
|
||||
{
|
||||
#if !display
|
||||
if (Context.defined("display")) return null;
|
||||
|
||||
var classType = Context.getLocalClass().get();
|
||||
var metaData = classType.meta.get();
|
||||
var metaData = classType.meta;
|
||||
var position = Context.currentPos();
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
for (meta in metaData)
|
||||
for (meta in metaData.extract(metaName))
|
||||
{
|
||||
if (meta.name == metaName)
|
||||
if (meta.params.length == 0) continue;
|
||||
|
||||
switch (meta.params[0].expr)
|
||||
{
|
||||
if (meta.params.length > 0)
|
||||
{
|
||||
switch (meta.params[0].expr)
|
||||
case EConst(CString("" | null)):
|
||||
return null;
|
||||
|
||||
case EConst(CString(filePath)):
|
||||
var path = filePath;
|
||||
|
||||
if (!FileSystem.exists(filePath))
|
||||
{
|
||||
case EConst(CString(filePath)):
|
||||
var path = filePath;
|
||||
|
||||
if (path == "") return null;
|
||||
if (path == null) return null;
|
||||
|
||||
if (!FileSystem.exists(filePath))
|
||||
{
|
||||
path = Context.resolvePath(filePath);
|
||||
}
|
||||
|
||||
if (!FileSystem.exists(path) || FileSystem.isDirectory(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = File.getBytes(path);
|
||||
var resourceName = "__ASSET__"
|
||||
+ metaName
|
||||
+ "_"
|
||||
+ (classType.pack.length > 0 ? classType.pack.join("_") + "_" : "")
|
||||
+ classType.name;
|
||||
|
||||
if (Context.getResources().exists(resourceName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (encode)
|
||||
{
|
||||
var resourceType = "image/png";
|
||||
|
||||
if (bytes.get(0) == 0xFF && bytes.get(1) == 0xD8)
|
||||
{
|
||||
resourceType = "image/jpg";
|
||||
}
|
||||
else if (bytes.get(0) == 0x47 && bytes.get(1) == 0x49 && bytes.get(2) == 0x46)
|
||||
{
|
||||
resourceType = "image/gif";
|
||||
}
|
||||
|
||||
var fieldValue = {pos: position, expr: EConst(CString(resourceType))};
|
||||
fields.push(
|
||||
{
|
||||
kind: FVar(macro:String, fieldValue),
|
||||
name: "resourceType",
|
||||
access: [APrivate, AStatic],
|
||||
pos: position
|
||||
});
|
||||
|
||||
var base64 = Base64.encode(bytes);
|
||||
Context.addResource(resourceName, Bytes.ofString(base64));
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.addResource(resourceName, bytes);
|
||||
}
|
||||
|
||||
var fieldValue = {pos: position, expr: EConst(CString(resourceName))};
|
||||
fields.push(
|
||||
{
|
||||
kind: FVar(macro:String, fieldValue),
|
||||
name: "resourceName",
|
||||
access: [APrivate, AStatic],
|
||||
pos: position
|
||||
});
|
||||
|
||||
return fields;
|
||||
|
||||
default:
|
||||
path = Context.resolvePath(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileSystem.exists(path) || FileSystem.isDirectory(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = File.getBytes(path);
|
||||
var resourceName = "__ASSET__"
|
||||
+ metaName
|
||||
+ "_"
|
||||
+ (classType.pack.length > 0 ? classType.pack.join("_") + "_" : "")
|
||||
+ classType.name;
|
||||
|
||||
if (Context.getResources().exists(resourceName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (encode)
|
||||
{
|
||||
var resourceType = "image/png";
|
||||
|
||||
if (bytes.get(0) == 0xFF && bytes.get(1) == 0xD8)
|
||||
{
|
||||
resourceType = "image/jpg";
|
||||
}
|
||||
else if (bytes.get(0) == 0x47 && bytes.get(1) == 0x49 && bytes.get(2) == 0x46)
|
||||
{
|
||||
resourceType = "image/gif";
|
||||
}
|
||||
|
||||
var definition = macro class Temp
|
||||
{
|
||||
private static inline var resourceType:String = $v{ resourceType };
|
||||
};
|
||||
|
||||
fields.push(definition.fields[0]);
|
||||
|
||||
var base64 = Base64.encode(bytes);
|
||||
Context.addResource(resourceName, Bytes.ofString(base64));
|
||||
}
|
||||
else
|
||||
{
|
||||
Context.addResource(resourceName, bytes);
|
||||
}
|
||||
|
||||
var definition = macro class Temp
|
||||
{
|
||||
private static inline var resourceName:String = $v{ resourceName };
|
||||
};
|
||||
|
||||
fields.push(definition.fields[0]);
|
||||
|
||||
return fields;
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
macro public static function embedFont():Array<Field>
|
||||
{
|
||||
if (Context.defined("display")) return Context.getBuildFields();
|
||||
|
||||
var fields = null;
|
||||
|
||||
var classType = Context.getLocalClass().get();
|
||||
@@ -250,7 +171,6 @@ class AssetsMacro
|
||||
var path = "";
|
||||
var glyphs = "32-255";
|
||||
|
||||
#if !display
|
||||
for (meta in metaData)
|
||||
{
|
||||
if (meta.name == ":font")
|
||||
@@ -275,10 +195,11 @@ class AssetsMacro
|
||||
|
||||
if (path != null && path != "")
|
||||
{
|
||||
#if html5
|
||||
Sys.command("haxelib", ["run", "lime", "generate", "-font-hash", sys.FileSystem.fullPath(path)]);
|
||||
path += ".hash";
|
||||
#end
|
||||
if (Context.defined("html5"))
|
||||
{
|
||||
Sys.command("haxelib", ["run", "lime", "generate", "-font-hash", sys.FileSystem.fullPath(path)]);
|
||||
path += ".hash";
|
||||
}
|
||||
|
||||
var bytes = File.getBytes(path);
|
||||
var resourceName = "LIME_font_" + (classType.pack.length > 0 ? classType.pack.join("_") + "_" : "") + classType.name;
|
||||
@@ -294,57 +215,44 @@ class AssetsMacro
|
||||
}
|
||||
}
|
||||
|
||||
var fieldValue = {pos: position, expr: EConst(CString(resourceName))};
|
||||
fields.push(
|
||||
{
|
||||
kind: FVar(macro:String, fieldValue),
|
||||
name: "resourceName",
|
||||
access: [APublic, AStatic],
|
||||
pos: position
|
||||
});
|
||||
var definition = macro class Temp
|
||||
{
|
||||
private static var resourceName:String = $v{ resourceName };
|
||||
|
||||
var constructor = macro
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
__fromBytes(haxe.Resource.getBytes(resourceName));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: [],
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
fields.push(definition.fields[0]);
|
||||
fields.push(definition.fields[1]);
|
||||
|
||||
return fields;
|
||||
}
|
||||
#end
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
macro public static function embedImage():Array<Field>
|
||||
{
|
||||
#if html5
|
||||
var fields = embedData(":image", true);
|
||||
#else
|
||||
var fields = embedData(":image");
|
||||
#end
|
||||
var fields = embedData(":image", Context.defined("html5"));
|
||||
if (fields == null) return null;
|
||||
|
||||
if (fields != null)
|
||||
var definition:TypeDefinition;
|
||||
if (Context.defined("html5"))
|
||||
{
|
||||
#if !display
|
||||
var constructor = macro
|
||||
definition = macro class Temp
|
||||
{
|
||||
public static var preload:js.html.Image;
|
||||
|
||||
public function new(?buffer:lime.graphics.ImageBuffer,
|
||||
?offsetX:Int, ?offsetY:Int, ?width:Int, ?height:Int,
|
||||
?color:Null<Int>, ?type:lime.graphics.ImageType,
|
||||
?onload:Dynamic = true)
|
||||
{
|
||||
#if html5
|
||||
super();
|
||||
|
||||
if (preload != null)
|
||||
@@ -371,91 +279,27 @@ class AssetsMacro
|
||||
}
|
||||
});
|
||||
}
|
||||
#else
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
definition = macro class Temp
|
||||
{
|
||||
public function new(?buffer:lime.graphics.ImageBuffer,
|
||||
?offsetX:Int, ?offsetY:Int, ?width:Int, ?height:Int,
|
||||
?color:Null<Int>, ?type:lime.graphics.ImageType)
|
||||
{
|
||||
super();
|
||||
|
||||
__fromBytes(haxe.Resource.getBytes(resourceName), null);
|
||||
#end
|
||||
};
|
||||
|
||||
var args = [
|
||||
{
|
||||
name: "buffer",
|
||||
opt: true,
|
||||
type: macro:lime.graphics.ImageBuffer,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "offsetX",
|
||||
opt: true,
|
||||
type: macro:Int,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "offsetY",
|
||||
opt: true,
|
||||
type: macro:Int,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "width",
|
||||
opt: true,
|
||||
type: macro:Int,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "height",
|
||||
opt: true,
|
||||
type: macro:Int,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
opt: true,
|
||||
type: macro:Null<Int>,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
opt: true,
|
||||
type: macro:lime.graphics.ImageType,
|
||||
value: null
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
#if html5
|
||||
args.push(
|
||||
{
|
||||
name: "onload",
|
||||
opt: true,
|
||||
type: macro:Dynamic,
|
||||
value: null
|
||||
});
|
||||
fields.push(
|
||||
{
|
||||
kind: FVar(macro:js.html.Image, null),
|
||||
name: "preload",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: [APublic, AStatic],
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: args,
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
for (field in definition.fields)
|
||||
{
|
||||
fields.push(field);
|
||||
}
|
||||
|
||||
return fields;
|
||||
@@ -464,54 +308,24 @@ class AssetsMacro
|
||||
macro public static function embedSound():Array<Field>
|
||||
{
|
||||
var fields = embedData(":sound");
|
||||
// CFFILoader.h(248) : NOT Implemented:api_buffer_data
|
||||
if (fields == null || Context.defined("html5") || !Context.defined("openfl"))
|
||||
return null;
|
||||
|
||||
if (fields != null)
|
||||
var definition = macro class Temp
|
||||
{
|
||||
#if (openfl && !html5 && !display) // CFFILoader.h(248) : NOT Implemented:api_buffer_data
|
||||
public function new(?stream:openfl.net.URLRequest,
|
||||
?context:openfl.media.SoundLoaderContext,
|
||||
?forcePlayAsMusic:Bool = false)
|
||||
{
|
||||
super();
|
||||
|
||||
var constructor = macro
|
||||
{
|
||||
super();
|
||||
var byteArray = openfl.utils.ByteArray.fromBytes(haxe.Resource.getBytes(resourceName));
|
||||
loadCompressedDataFromByteArray(byteArray, byteArray.length, forcePlayAsMusic);
|
||||
}
|
||||
};
|
||||
|
||||
var byteArray = openfl.utils.ByteArray.fromBytes(haxe.Resource.getBytes(resourceName));
|
||||
loadCompressedDataFromByteArray(byteArray, byteArray.length, forcePlayAsMusic);
|
||||
};
|
||||
|
||||
var args = [
|
||||
{
|
||||
name: "stream",
|
||||
opt: true,
|
||||
type: macro:openfl.net.URLRequest,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "context",
|
||||
opt: true,
|
||||
type: macro:openfl.media.SoundLoaderContext,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
name: "forcePlayAsMusic",
|
||||
opt: true,
|
||||
type: macro:Bool,
|
||||
value: macro false
|
||||
}
|
||||
];
|
||||
fields.push(
|
||||
{
|
||||
name: "new",
|
||||
access: [APublic],
|
||||
kind: FFun(
|
||||
{
|
||||
args: args,
|
||||
expr: constructor,
|
||||
params: [],
|
||||
ret: null
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
}
|
||||
fields.push(definition.fields[0]);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
@@ -226,15 +226,6 @@ class Application extends Module
|
||||
**/
|
||||
public function onJoystickHatMove(joystick:Joystick, hat:Int, position:JoystickHatPosition):Void {}
|
||||
|
||||
/**
|
||||
Called when a joystick axis move event is fired
|
||||
@param joystick The current joystick
|
||||
@param trackball The trackball that was moved
|
||||
@param x The x movement of the trackball (between 0 and 1)
|
||||
@param y The y movement of the trackball (between 0 and 1)
|
||||
**/
|
||||
public function onJoystickTrackballMove(joystick:Joystick, trackball:Int, x:Float, y:Float):Void {}
|
||||
|
||||
/**
|
||||
Called when a key down event is fired on the primary window
|
||||
@param keyCode The code of the key that was pressed
|
||||
@@ -585,7 +576,6 @@ class Application extends Module
|
||||
joystick.onButtonUp.add(onJoystickButtonUp.bind(joystick));
|
||||
joystick.onDisconnect.add(onJoystickDisconnect.bind(joystick));
|
||||
joystick.onHatMove.add(onJoystickHatMove.bind(joystick));
|
||||
joystick.onTrackballMove.add(onJoystickTrackballMove.bind(joystick));
|
||||
}
|
||||
|
||||
@:noCompletion private function __onModuleExit(code:Int):Void
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.app;
|
||||
|
||||
import lime.system.System;
|
||||
import lime.system.ThreadPool;
|
||||
import lime.system.WorkOutput;
|
||||
import lime.utils.Log;
|
||||
|
||||
/**
|
||||
@@ -66,34 +67,24 @@ import lime.utils.Log;
|
||||
@:noCompletion private var __progressListeners:Array<Int->Int->Void>;
|
||||
|
||||
/**
|
||||
Create a new `Future` instance
|
||||
@param work (Optional) A function to execute
|
||||
@param async (Optional) If a function is specified, whether to execute it asynchronously where supported
|
||||
@param work Deprecated; use `Future.withEventualValue()` instead.
|
||||
@param useThreads Deprecated; use `Future.withEventualValue()` instead.
|
||||
**/
|
||||
public function new(work:Void->T = null, async:Bool = false)
|
||||
public function new(work:WorkFunction<Void->T> = null, useThreads:Bool = false)
|
||||
{
|
||||
if (work != null)
|
||||
{
|
||||
if (async)
|
||||
{
|
||||
var promise = new Promise<T>();
|
||||
promise.future = this;
|
||||
var promise = new Promise<T>();
|
||||
promise.future = this;
|
||||
|
||||
FutureWork.queue({promise: promise, work: work});
|
||||
}
|
||||
else
|
||||
#if (lime_threads && html5)
|
||||
if (useThreads)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = work();
|
||||
isComplete = true;
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
error = e;
|
||||
isError = true;
|
||||
}
|
||||
work.makePortable();
|
||||
}
|
||||
#end
|
||||
|
||||
FutureWork.run(dispatchWorkFunction, work, promise, useThreads ? MULTI_THREADED : SINGLE_THREADED, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +95,9 @@ import lime.utils.Log;
|
||||
{
|
||||
var promise = new Promise<T>();
|
||||
|
||||
onComplete.add(function(data) promise.complete(data), true);
|
||||
if (onError != null) onError.add(function(error) promise.error(error), true);
|
||||
if (onProgress != null) onProgress.add(function(progress, total) promise.progress(progress, total), true);
|
||||
onComplete.add(promise.complete, true);
|
||||
if (onError != null) onError.add(promise.error, true);
|
||||
if (onProgress != null) onProgress.add(promise.progress, true);
|
||||
|
||||
return promise.future;
|
||||
}
|
||||
@@ -198,17 +189,6 @@ import lime.utils.Log;
|
||||
**/
|
||||
public function ready(waitTime:Int = -1):Future<T>
|
||||
{
|
||||
#if js
|
||||
if (isComplete || isError)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.warn("Cannot block thread in JavaScript");
|
||||
return this;
|
||||
}
|
||||
#else
|
||||
if (isComplete || isError)
|
||||
{
|
||||
return this;
|
||||
@@ -216,20 +196,34 @@ import lime.utils.Log;
|
||||
else
|
||||
{
|
||||
var time = System.getTimer();
|
||||
var prevTime = time;
|
||||
var end = time + waitTime;
|
||||
|
||||
while (!isComplete && !isError && time <= end)
|
||||
{
|
||||
#if sys
|
||||
Sys.sleep(0.01);
|
||||
#end
|
||||
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
|
||||
}
|
||||
|
||||
prevTime = time;
|
||||
time = System.getTimer();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +295,7 @@ import lime.utils.Log;
|
||||
|
||||
/**
|
||||
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
|
||||
**/
|
||||
public static function withValue<T>(value:T):Future<T>
|
||||
@@ -311,50 +305,202 @@ 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<T>(work:WorkFunction<State -> Null<T>>, state:State, mode:ThreadMode = #if html5 SINGLE_THREADED #else MULTI_THREADED #end):Future<T>
|
||||
{
|
||||
var future = new Future<T>();
|
||||
var promise = new Promise<T>();
|
||||
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<T>(work:WorkFunction<Void -> T>):Null<T>
|
||||
{
|
||||
return work.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The class that handles asynchronous `work` functions passed to `new Future()`.
|
||||
**/
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
@:dox(hide) private class FutureWork
|
||||
@:dox(hide) class FutureWork
|
||||
{
|
||||
private static var threadPool:ThreadPool;
|
||||
@: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<Dynamic>`. Instead, we'll store
|
||||
// the two methods we need.
|
||||
private static var promises:Map<Int, {complete:Dynamic -> Dynamic, error:Dynamic -> Dynamic}> = new Map();
|
||||
#end
|
||||
public static var minThreads(default, set):Int = 0;
|
||||
public static var maxThreads(default, set):Int = 1;
|
||||
public static var activeJobs(get, never):Int;
|
||||
|
||||
public static function queue(state:Dynamic = null):Void
|
||||
private static function getPool(mode:ThreadMode):ThreadPool
|
||||
{
|
||||
if (threadPool == null)
|
||||
{
|
||||
threadPool = new ThreadPool();
|
||||
threadPool.doWork.add(threadPool_doWork);
|
||||
threadPool.onComplete.add(threadPool_onComplete);
|
||||
threadPool.onError.add(threadPool_onError);
|
||||
#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;
|
||||
}
|
||||
|
||||
threadPool.queue(state);
|
||||
@:allow(lime.app.Future)
|
||||
private static function run<T>(work:WorkFunction<State->Null<T>>, state:State, promise:Promise<T>, mode:ThreadMode = MULTI_THREADED, legacyCode:Bool = false):Void
|
||||
{
|
||||
var bundle = {work: work, state: state, promise: promise, legacyCode: legacyCode};
|
||||
|
||||
#if lime_threads
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
#if html5
|
||||
work.makePortable();
|
||||
#end
|
||||
|
||||
bundle.promise = null;
|
||||
}
|
||||
#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
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
private static function threadPool_doWork(state:Dynamic):Void
|
||||
private static function threadPool_doWork(bundle:{work:WorkFunction<State->Dynamic>, state:State, legacyCode:Bool}, output:WorkOutput):Void
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = state.work();
|
||||
threadPool.sendComplete({promise: state.promise, result: result});
|
||||
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)
|
||||
{
|
||||
threadPool.sendError({promise: state.promise, error: e});
|
||||
#if (lime_threads && html5)
|
||||
bundle.work.makePortable();
|
||||
#end
|
||||
output.sendError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static function threadPool_onComplete(state:Dynamic):Void
|
||||
private static function singleThreadPool_onComplete(result:Dynamic):Void
|
||||
{
|
||||
state.promise.complete(state.result);
|
||||
singleThreadPool.activeJob.state.promise.complete(result);
|
||||
}
|
||||
|
||||
private static function threadPool_onError(state:Dynamic):Void
|
||||
private static function singleThreadPool_onError(error:Dynamic):Void
|
||||
{
|
||||
state.promise.error(state.error);
|
||||
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);
|
||||
promise.complete(result);
|
||||
}
|
||||
|
||||
private static function multiThreadPool_onError(error:Dynamic):Void
|
||||
{
|
||||
var promise = promises[multiThreadPool.activeJob.id];
|
||||
promises.remove(multiThreadPool.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)
|
||||
{
|
||||
multiThreadPool.minThreads = value;
|
||||
}
|
||||
#end
|
||||
return minThreads = value;
|
||||
}
|
||||
|
||||
@:noCompletion private static inline function set_maxThreads(value:Int):Int
|
||||
{
|
||||
if (singleThreadPool != null)
|
||||
{
|
||||
singleThreadPool.maxThreads = value;
|
||||
}
|
||||
#if lime_threads
|
||||
if (multiThreadPool != null)
|
||||
{
|
||||
multiThreadPool.maxThreads = value;
|
||||
}
|
||||
#end
|
||||
return maxThreads = value;
|
||||
}
|
||||
|
||||
@:noCompletion private static 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ package lime.app;
|
||||
`Future` values.
|
||||
|
||||
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
|
||||
function examplePromise ():Future<String> {
|
||||
@@ -44,7 +44,7 @@ package lime.app;
|
||||
@:noDebug
|
||||
#end
|
||||
@:allow(lime.app.Future)
|
||||
#if (!hl && !js)
|
||||
#if (!hl && !js && !macro)
|
||||
@:generic
|
||||
#end
|
||||
class Promise<T>
|
||||
|
||||
@@ -32,10 +32,11 @@ import lime.utils.UInt8Array;
|
||||
#if !display
|
||||
import lime._internal.backend.html5.HTML5HTTPRequest;
|
||||
#end
|
||||
import js.html.CanvasElement;
|
||||
import js.html.ImageElement;
|
||||
import js.html.Image as JSImage;
|
||||
import js.Browser;
|
||||
import js.html.CanvasElement;
|
||||
import js.html.Image as JSImage;
|
||||
import js.html.ImageElement;
|
||||
import lime._internal.backend.html5.HTML5Thread;
|
||||
#elseif flash
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
@@ -230,6 +231,13 @@ class Image
|
||||
{
|
||||
#if (js && html5)
|
||||
type = CANVAS;
|
||||
|
||||
#if lime_threads
|
||||
if (HTML5Thread.current().isWorker())
|
||||
{
|
||||
type = DATA;
|
||||
}
|
||||
#end
|
||||
#elseif flash
|
||||
type = FLASH;
|
||||
#else
|
||||
@@ -994,7 +1002,7 @@ class Image
|
||||
|
||||
return promise.future;
|
||||
#else
|
||||
return new Future<Image>(function() return fromBytes(bytes), true);
|
||||
return Future.withEventualValue(fromBytes, bytes, MULTI_THREADED);
|
||||
#end
|
||||
}
|
||||
|
||||
|
||||
@@ -762,11 +762,7 @@ class Cairo
|
||||
private static function get_versionString():String
|
||||
{
|
||||
#if (lime_cffi && lime_cairo && !macro)
|
||||
#if hl
|
||||
return @:privateAccess String.fromUTF8(NativeCFFI.lime_cairo_version_string());
|
||||
#else
|
||||
return NativeCFFI.lime_cairo_version_string();
|
||||
#end
|
||||
return CFFI.stringValue(NativeCFFI.lime_cairo_version_string());
|
||||
#else
|
||||
return "";
|
||||
#end
|
||||
|
||||
@@ -10,10 +10,11 @@ package lime.math;
|
||||
|
||||
```
|
||||
[ a, c, tx ]
|
||||
[ c, d, ty ]
|
||||
[ b, d, ty ]
|
||||
[ 0, 0, 1 ]
|
||||
```
|
||||
**/
|
||||
import lime.utils.Float32Array;
|
||||
#if hl
|
||||
@:keep
|
||||
#end
|
||||
@@ -21,37 +22,37 @@ package lime.math;
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
class Matrix3
|
||||
abstract Matrix3(Float32Array) from Float32Array to Float32Array
|
||||
{
|
||||
/**
|
||||
The matrix a component, used in scaling and skewing (default is 1)
|
||||
**/
|
||||
public var a:Float;
|
||||
public var a(get, set):Float;
|
||||
|
||||
/**
|
||||
The matrix b component, used in rotation and skewing (default is 0)
|
||||
**/
|
||||
public var b:Float;
|
||||
public var b(get, set):Float;
|
||||
|
||||
/**
|
||||
The matrix c component, used in rotation and skewing (default is 0)
|
||||
**/
|
||||
public var c:Float;
|
||||
public var c(get, set):Float;
|
||||
|
||||
/**
|
||||
The matrix d component, used in scaling and skewing (default is 1)
|
||||
**/
|
||||
public var d:Float;
|
||||
public var d(get, set):Float;
|
||||
|
||||
/**
|
||||
The matrix tx component, used in translation (default is 0)
|
||||
**/
|
||||
public var tx:Float;
|
||||
public var tx(get, set):Float;
|
||||
|
||||
/**
|
||||
The matrix ty component, used in translation (default is 0)
|
||||
**/
|
||||
public var ty:Float;
|
||||
public var ty(get, set):Float;
|
||||
|
||||
private static var __identity = new Matrix3();
|
||||
|
||||
@@ -66,12 +67,11 @@ class Matrix3
|
||||
**/
|
||||
public function new(a:Float = 1, b:Float = 0, c:Float = 0, d:Float = 1, tx:Float = 0, ty:Float = 0)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.d = d;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
this = new Float32Array([
|
||||
a, c, 0,
|
||||
b, d, 0,
|
||||
tx, ty, 1
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,10 +245,10 @@ class Matrix3
|
||||
@param scaleX An x scale transformation value
|
||||
@param scaleY A y scale transformation value
|
||||
@param rotation (Optional) A rotation value (default is 0)
|
||||
@param tx (Optional) A translate x value (default is 0)
|
||||
@param ty (Optional) A translate y value (default is 0)
|
||||
@param xTranslate (Optional) A translate x value (default is 0)
|
||||
@param yTranslate (Optional) A translate y value (default is 0)
|
||||
**/
|
||||
public function createBox(scaleX:Float, scaleY:Float, rotation:Float = 0, tx:Float = 0, ty:Float = 0):Void
|
||||
public function createBox(scaleX:Float, scaleY:Float, rotation:Float = 0, xTranslate:Float = 0, yTranslate:Float = 0):Void
|
||||
{
|
||||
if (rotation != 0)
|
||||
{
|
||||
@@ -268,8 +268,8 @@ class Matrix3
|
||||
d = scaleY;
|
||||
}
|
||||
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
tx = xTranslate;
|
||||
ty = yTranslate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,11 +277,11 @@ class Matrix3
|
||||
@param width The width of the gradient fill
|
||||
@param height The height of the gradient fill
|
||||
@param rotation (Optional) A rotation for the gradient fill (default is 0)
|
||||
@param tx (Optional) An x offset for the gradient fill (default is 0)
|
||||
@param ty (Optional) A y offset for the gradient fill (default is 0)
|
||||
@param xTranslate (Optional) An x offset for the gradient fill (default is 0)
|
||||
@param yTranslate (Optional) A y offset for the gradient fill (default is 0)
|
||||
@return A new `Matrix` instance
|
||||
**/
|
||||
public function createGradientBox(width:Float, height:Float, rotation:Float = 0, tx:Float = 0, ty:Float = 0):Void
|
||||
public function createGradientBox(width:Float, height:Float, rotation:Float = 0, xTranslate:Float = 0, yTranslate:Float = 0):Void
|
||||
{
|
||||
a = width / 1638.4;
|
||||
d = height / 1638.4;
|
||||
@@ -303,8 +303,8 @@ class Matrix3
|
||||
c = 0;
|
||||
}
|
||||
|
||||
this.tx = tx + width / 2;
|
||||
this.ty = ty + height / 2;
|
||||
tx = xTranslate + width / 2;
|
||||
ty = yTranslate + height / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,12 +459,12 @@ class Matrix3
|
||||
**/
|
||||
public #if !js inline #end function setTo(a:Float, b:Float, c:Float, d:Float, tx:Float, ty:Float):Void
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.d = d;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
set_a(a);
|
||||
set_b(b);
|
||||
set_c(c);
|
||||
set_d(d);
|
||||
set_tx(tx);
|
||||
set_ty(ty);
|
||||
}
|
||||
|
||||
@:dox(hide) @:noCompletion public inline function to3DString(roundPixels:Bool = false):String
|
||||
@@ -574,4 +574,69 @@ class Matrix3
|
||||
tx += dx;
|
||||
ty += dy;
|
||||
}
|
||||
|
||||
inline function get_a():Float
|
||||
{
|
||||
return this[0];
|
||||
}
|
||||
inline function set_a(value: Float):Float
|
||||
{
|
||||
return this[0] = value;
|
||||
}
|
||||
|
||||
inline function get_b():Float
|
||||
{
|
||||
return this[3];
|
||||
}
|
||||
inline function set_b(value: Float):Float
|
||||
{
|
||||
return this[3] = value;
|
||||
}
|
||||
|
||||
inline function get_c():Float
|
||||
{
|
||||
return this[1];
|
||||
}
|
||||
inline function set_c(value: Float):Float
|
||||
{
|
||||
return this[1] = value;
|
||||
}
|
||||
|
||||
inline function get_d():Float
|
||||
{
|
||||
return this[4];
|
||||
}
|
||||
inline function set_d(value: Float):Float
|
||||
{
|
||||
return this[4] = value;
|
||||
}
|
||||
|
||||
inline function get_tx():Float
|
||||
{
|
||||
return this[6];
|
||||
}
|
||||
inline function set_tx(value: Float):Float
|
||||
{
|
||||
return this[6] = value;
|
||||
}
|
||||
|
||||
inline function get_ty():Float
|
||||
{
|
||||
return this[7];
|
||||
}
|
||||
inline function set_ty(value: Float):Float
|
||||
{
|
||||
return this[7] = value;
|
||||
}
|
||||
|
||||
@:dox(hide) @:noCompletion @:arrayAccess public function get(index:Int):Float
|
||||
{
|
||||
return this[index];
|
||||
}
|
||||
|
||||
@:dox(hide) @:noCompletion @:arrayAccess public function set(index:Int, value:Float):Float
|
||||
{
|
||||
this[index] = value;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,46 @@ abstract Matrix4(Float32Array) from Float32Array to Float32Array
|
||||
this[14] = -(zNear + zFar) * sz;
|
||||
this[15] = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes this matrix with values for a perspective projection
|
||||
@param fov The field of view
|
||||
@param aspect The aspect ratio
|
||||
@param zNear The near depth-clipping plane position
|
||||
@param zFar The far depth-clipping plane position
|
||||
**/
|
||||
public function createPerspective(fov:Float, aspect:Float, zNear:Float, zFar:Float):Void
|
||||
{
|
||||
if (aspect > -0.0000001 && aspect < 0.0000001)
|
||||
{
|
||||
throw "Aspect ratio may not be 0";
|
||||
}
|
||||
|
||||
var top = fov * zNear;
|
||||
var bottom = -top;
|
||||
var right = top * aspect;
|
||||
var left = -right;
|
||||
|
||||
this[0] = 2.0 * zNear / (right - left);
|
||||
this[1] = 0;
|
||||
this[2] = 0;
|
||||
this[3] = 0;
|
||||
|
||||
this[4] = 0;
|
||||
this[5] = 2.0 * zNear / (top - bottom);
|
||||
this[6] = 0;
|
||||
this[7] = 0;
|
||||
|
||||
this[8] = (right + left) / (right - left);
|
||||
this[9] = (top + bottom) / (top - bottom);
|
||||
this[10] = -(zFar + zNear) / (zFar - zNear);
|
||||
this[11] = -1.0;
|
||||
|
||||
this[12] = 0;
|
||||
this[13] = 0;
|
||||
this[14] = -2 * zFar * zNear / (zFar - zNear);
|
||||
this[15] = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transformation matrix's translation, rotation, and scale settings as a Vector of three Vector4 objects.
|
||||
|
||||
@@ -309,9 +309,9 @@ class AudioBuffer
|
||||
|
||||
public static function loadFromFiles(paths:Array<String>):Future<AudioBuffer>
|
||||
{
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var promise = new Promise<AudioBuffer>();
|
||||
|
||||
#if (js && html5 && lime_howlerjs)
|
||||
var audioBuffer = AudioBuffer.fromFiles(paths);
|
||||
|
||||
if (audioBuffer != null)
|
||||
@@ -332,11 +332,11 @@ class AudioBuffer
|
||||
{
|
||||
promise.error(null);
|
||||
}
|
||||
#else
|
||||
promise.completeWith(new Future<AudioBuffer>(function() return fromFiles(paths), true));
|
||||
#end
|
||||
|
||||
return promise.future;
|
||||
#else
|
||||
return Future.withEventualValue(fromFiles, paths, MULTI_THREADED);
|
||||
#end
|
||||
}
|
||||
|
||||
private static function __getCodec(bytes:Bytes):String
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.media.openal;
|
||||
|
||||
#if (!lime_doc_gen || lime_openal)
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.CFFIPointer;
|
||||
import lime.utils.ArrayBufferView;
|
||||
|
||||
@@ -998,10 +999,7 @@ class AL
|
||||
{
|
||||
#if (lime_cffi && lime_openal && !macro)
|
||||
var result = NativeCFFI.lime_al_get_string(param);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.media.openal;
|
||||
|
||||
#if (!lime_doc_gen || lime_openal)
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.CFFIPointer;
|
||||
|
||||
#if !lime_debug
|
||||
@@ -144,10 +145,7 @@ class ALC
|
||||
{
|
||||
#if (lime_cffi && lime_openal && !macro)
|
||||
var result = NativeCFFI.lime_alc_get_string(device, param);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
|
||||
@@ -3,6 +3,7 @@ package lime.net.curl;
|
||||
#if (!lime_doc_gen || lime_curl)
|
||||
import haxe.io.Bytes;
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.CFFIPointer;
|
||||
|
||||
#if !lime_debug
|
||||
@@ -57,10 +58,7 @@ class CURL
|
||||
{
|
||||
#if (lime_cffi && lime_curl && !macro)
|
||||
var result = NativeCFFI.lime_curl_easy_escape(handle, url, length);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -187,17 +185,10 @@ class CURL
|
||||
|
||||
case CURLOption.HEADERFUNCTION:
|
||||
var callback:CURL->String->Void = cast parameter;
|
||||
#if hl
|
||||
parameter = function(header:hl.Bytes)
|
||||
parameter = function(header)
|
||||
{
|
||||
callback(this, @:privateAccess String.fromUTF8(header));
|
||||
callback(this, CFFI.stringValue(header));
|
||||
}
|
||||
#else
|
||||
parameter = function(header:String)
|
||||
{
|
||||
callback(this, header);
|
||||
}
|
||||
#end
|
||||
|
||||
case CURLOption.HTTPHEADER:
|
||||
#if hl
|
||||
@@ -221,10 +212,7 @@ class CURL
|
||||
{
|
||||
#if (lime_cffi && lime_curl && !macro)
|
||||
var result = NativeCFFI.lime_curl_easy_strerror(cast(code, Int));
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -234,10 +222,7 @@ class CURL
|
||||
{
|
||||
#if (lime_cffi && lime_curl && !macro)
|
||||
var result = NativeCFFI.lime_curl_easy_unescape(handle, url, inLength, outLength);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
@@ -247,10 +232,7 @@ class CURL
|
||||
{
|
||||
#if (lime_cffi && lime_curl && !macro)
|
||||
var result = NativeCFFI.lime_curl_version();
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
|
||||
@@ -1,170 +1,4 @@
|
||||
package lime.system;
|
||||
|
||||
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
|
||||
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 Event<Dynamic->Void>();
|
||||
public var onComplete = new Event<Dynamic->Void>();
|
||||
public var onError = new Event<Dynamic->Void>();
|
||||
public var onProgress = new Event<Dynamic->Void>();
|
||||
|
||||
@:noCompletion private var __runMessage:Dynamic;
|
||||
#if (cpp || neko)
|
||||
@:noCompletion private var __messageQueue:Deque<Dynamic>;
|
||||
@: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<Dynamic>();
|
||||
__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
|
||||
}
|
||||
}
|
||||
@:deprecated("Replace references to lime.system.BackgroundWorker with lime.system.ThreadPool. As the API is identical, no other changes are necessary.")
|
||||
typedef BackgroundWorker = ThreadPool;
|
||||
|
||||
@@ -155,15 +155,15 @@ class CFFI
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var haxelib = __findHaxelib("lime");
|
||||
var ndllFolder = __findNDLLFolder();
|
||||
|
||||
if (haxelib != "")
|
||||
if (ndllFolder != "")
|
||||
{
|
||||
result = __tryLoad(haxelib + "/ndll/" + __sysName() + "/" + library, library, method, args);
|
||||
result = __tryLoad(ndllFolder + __sysName() + "/" + library, library, method, args);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
result = __tryLoad(haxelib + "/ndll/" + __sysName() + "64/" + library, library, method, args);
|
||||
result = __tryLoad(ndllFolder + __sysName() + "64/" + library, library, method, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,39 +204,36 @@ class CFFI
|
||||
#end
|
||||
}
|
||||
|
||||
private static function __findHaxelib(library:String):String
|
||||
@:dox(hide) #if !hl inline #end public static function stringValue(#if hl value:hl.Bytes #else value:String #end):String
|
||||
{
|
||||
#if hl
|
||||
return value != null ? @:privateAccess String.fromUTF8(value) : null;
|
||||
#else
|
||||
return value;
|
||||
#end
|
||||
}
|
||||
|
||||
private static function __findNDLLFolder():String
|
||||
{
|
||||
#if (sys && !macro && !html5)
|
||||
var process = new Process("haxelib", ["path", "lime"]);
|
||||
|
||||
try
|
||||
{
|
||||
var proc = new Process("haxelib", ["path", library]);
|
||||
|
||||
if (proc != null)
|
||||
while (true)
|
||||
{
|
||||
var stream = proc.stdout;
|
||||
var line = StringTools.trim(process.stdout.readLine());
|
||||
|
||||
try
|
||||
if (StringTools.startsWith(line, "-L "))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var s = stream.readLine();
|
||||
|
||||
if (s.substr(0, 1) != "-")
|
||||
{
|
||||
stream.close();
|
||||
proc.close();
|
||||
__loaderTrace("Found haxelib " + s);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
process.close();
|
||||
return Path.addTrailingSlash(line.substr(3));
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
|
||||
stream.close();
|
||||
proc.close();
|
||||
}
|
||||
}
|
||||
catch (e:Dynamic) {}
|
||||
|
||||
process.close();
|
||||
#end
|
||||
|
||||
return "";
|
||||
@@ -308,7 +305,7 @@ class CFFI
|
||||
}
|
||||
else if (!lazy)
|
||||
{
|
||||
var ndllFolder = __findHaxelib("lime") + "/ndll/" + __sysName();
|
||||
var ndllFolder = __findNDLLFolder() + __sysName();
|
||||
throw "Could not find lime.ndll. This file is provided with Lime's Haxelib releases, but not via Git. "
|
||||
+ "Please copy it from Lime's latest Haxelib release into either "
|
||||
+ ndllFolder + " or " + ndllFolder + "64, as appropriate for your system. "
|
||||
|
||||
@@ -3,6 +3,7 @@ package lime.system;
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.app.Application;
|
||||
import lime.app.Event;
|
||||
import lime.system.CFFI;
|
||||
#if flash
|
||||
import flash.desktop.Clipboard as FlashClipboard;
|
||||
#elseif (js && html5)
|
||||
@@ -39,15 +40,7 @@ class Clipboard
|
||||
_text = null;
|
||||
|
||||
#if (lime_cffi && !macro)
|
||||
#if hl
|
||||
var utf = NativeCFFI.lime_clipboard_get_text();
|
||||
if (utf != null)
|
||||
{
|
||||
_text = @:privateAccess String.fromUTF8(utf);
|
||||
}
|
||||
#else
|
||||
_text = NativeCFFI.lime_clipboard_get_text();
|
||||
#end
|
||||
_text = CFFI.stringValue(NativeCFFI.lime_clipboard_get_text());
|
||||
#elseif flash
|
||||
if (FlashClipboard.generalClipboard.hasFormat(TEXT_FORMAT))
|
||||
{
|
||||
|
||||
@@ -68,8 +68,7 @@ abstract Locale(String) from String to String
|
||||
locale = toString(getDefault());
|
||||
#elseif (lime_cffi && !macro)
|
||||
#if hl
|
||||
var _locale = lime_locale_get_system_locale();
|
||||
locale = _locale != null ? @:privateAccess String.fromUTF8(_locale) : null;
|
||||
locale = CFFI.stringValue(lime_locale_get_system_locale());
|
||||
#else
|
||||
locale = CFFI.load("lime", "lime_locale_get_system_locale", 0)();
|
||||
#end
|
||||
|
||||
@@ -35,9 +35,9 @@ import sys.io.Process;
|
||||
@:access(lime._internal.backend.native.NativeCFFI)
|
||||
@:access(lime.system.Display)
|
||||
@:access(lime.system.DisplayMode)
|
||||
#if (cpp && windows && !HXCPP_MINGW && !lime_disable_gpu_hint)
|
||||
#if (cpp && windows && !lime_disable_gpu_hint)
|
||||
@:cppFileCode('
|
||||
#if defined(HX_WINDOWS)
|
||||
#if defined(HX_WINDOWS) && !defined(__MINGW32__)
|
||||
extern "C" {
|
||||
_declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
||||
_declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
@@ -233,11 +233,7 @@ class System
|
||||
{
|
||||
var display = new Display();
|
||||
display.id = id;
|
||||
#if hl
|
||||
display.name = @:privateAccess String.fromUTF8(displayInfo.name);
|
||||
#else
|
||||
display.name = displayInfo.name;
|
||||
#end
|
||||
display.name = CFFI.stringValue(displayInfo.name);
|
||||
display.bounds = new Rectangle(displayInfo.bounds.x, displayInfo.bounds.y, displayInfo.bounds.width, displayInfo.bounds.height);
|
||||
|
||||
#if ios
|
||||
@@ -445,19 +441,11 @@ class System
|
||||
}
|
||||
}
|
||||
|
||||
#if hl
|
||||
path = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_directory(type, company, file));
|
||||
#else
|
||||
path = NativeCFFI.lime_system_get_directory(type, company, file);
|
||||
#end
|
||||
path = CFFI.stringValue(NativeCFFI.lime_system_get_directory(type, company, file));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if hl
|
||||
path = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_directory(type, null, null));
|
||||
#else
|
||||
path = NativeCFFI.lime_system_get_directory(type, null, null);
|
||||
#end
|
||||
path = CFFI.stringValue(NativeCFFI.lime_system_get_directory(type, null, null));
|
||||
}
|
||||
|
||||
#if windows
|
||||
@@ -669,11 +657,7 @@ class System
|
||||
if (__deviceModel == null)
|
||||
{
|
||||
#if (lime_cffi && !macro && (windows || ios || tvos))
|
||||
#if hl
|
||||
__deviceModel = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_device_model());
|
||||
#else
|
||||
__deviceModel = NativeCFFI.lime_system_get_device_model();
|
||||
#end
|
||||
__deviceModel = CFFI.stringValue(NativeCFFI.lime_system_get_device_model());
|
||||
#elseif android
|
||||
var manufacturer:String = JNI.createStaticField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").get();
|
||||
var model:String = JNI.createStaticField("android/os/Build", "MODEL", "Ljava/lang/String;").get();
|
||||
@@ -704,11 +688,7 @@ class System
|
||||
if (__deviceVendor == null)
|
||||
{
|
||||
#if (lime_cffi && !macro && windows && !html5)
|
||||
#if hl
|
||||
__deviceVendor = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_device_vendor());
|
||||
#else
|
||||
__deviceVendor = NativeCFFI.lime_system_get_device_vendor();
|
||||
#end
|
||||
__deviceVendor = CFFI.stringValue(NativeCFFI.lime_system_get_device_vendor());
|
||||
#elseif android
|
||||
var vendor:String = JNI.createStaticField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").get();
|
||||
if (vendor != null)
|
||||
@@ -790,11 +770,7 @@ class System
|
||||
if (__platformLabel == null)
|
||||
{
|
||||
#if (lime_cffi && !macro && windows && !html5)
|
||||
#if hl
|
||||
var label:String = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_platform_label());
|
||||
#else
|
||||
var label:String = NativeCFFI.lime_system_get_platform_label();
|
||||
#end
|
||||
var label:String = CFFI.stringValue(NativeCFFI.lime_system_get_platform_label());
|
||||
if (label != null) __platformLabel = StringTools.trim(label);
|
||||
#elseif linux
|
||||
__platformLabel = __runProcess("lsb_release", ["-ds"]);
|
||||
@@ -852,11 +828,7 @@ class System
|
||||
if (__platformVersion == null)
|
||||
{
|
||||
#if (lime_cffi && !macro && windows && !html5)
|
||||
#if hl
|
||||
__platformVersion = @:privateAccess String.fromUTF8(NativeCFFI.lime_system_get_platform_version());
|
||||
#else
|
||||
__platformVersion = NativeCFFI.lime_system_get_platform_version();
|
||||
#end
|
||||
__platformVersion = CFFI.stringValue(NativeCFFI.lime_system_get_platform_version());
|
||||
#elseif android
|
||||
var release = JNI.createStaticField("android/os/Build$VERSION", "RELEASE", "Ljava/lang/String;").get();
|
||||
var api = JNI.createStaticField("android/os/Build$VERSION", "SDK_INT", "I").get();
|
||||
|
||||
@@ -1,244 +1,745 @@
|
||||
package lime.system;
|
||||
|
||||
import haxe.Constraints.Function;
|
||||
import lime.app.Application;
|
||||
import lime.app.Event;
|
||||
#if sys
|
||||
#if haxe4
|
||||
import sys.thread.Deque;
|
||||
import lime.system.WorkOutput;
|
||||
import lime.utils.Log;
|
||||
#if target.threaded
|
||||
import sys.thread.Thread;
|
||||
#elseif (cpp || webassembly)
|
||||
import cpp.vm.Deque;
|
||||
import cpp.vm.Thread;
|
||||
#elseif neko
|
||||
import neko.vm.Deque;
|
||||
import neko.vm.Thread;
|
||||
#elseif html5
|
||||
import lime._internal.backend.html5.HTML5Thread as Thread;
|
||||
#end
|
||||
#end
|
||||
|
||||
/**
|
||||
A simple and thread-safe way to run a one or more asynchronous jobs. It
|
||||
manages a queue of jobs, starting new ones once the old ones are done.
|
||||
|
||||
It can also keep a certain number of threads (configurable via `minThreads`)
|
||||
running in the background even when no jobs are available. This avoids the
|
||||
not-insignificant overhead of stopping and restarting threads.
|
||||
|
||||
Sample usage:
|
||||
|
||||
var threadPool:ThreadPool = new ThreadPool();
|
||||
threadPool.onComplete.add(onFileProcessed);
|
||||
|
||||
threadPool.maxThreads = 3;
|
||||
for(url in urls)
|
||||
{
|
||||
threadPool.run(processFile, url);
|
||||
}
|
||||
|
||||
For thread safety, the worker function should only give output through the
|
||||
`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.
|
||||
**/
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
class ThreadPool
|
||||
class ThreadPool extends WorkOutput
|
||||
{
|
||||
public var currentThreads(default, null):Int;
|
||||
public var doWork = new Event<Dynamic->Void>();
|
||||
public var maxThreads:Int;
|
||||
public var minThreads:Int;
|
||||
public var onComplete = new Event<Dynamic->Void>();
|
||||
public var onError = new Event<Dynamic->Void>();
|
||||
public var onProgress = new Event<Dynamic->Void>();
|
||||
public var onRun = new Event<Dynamic->Void>();
|
||||
#if (haxe4 && lime_threads)
|
||||
/**
|
||||
A thread or null value to be compared against `Thread.current()`. Don't
|
||||
do anything with this other than check for equality.
|
||||
|
||||
#if (cpp || neko || webassembly)
|
||||
@:noCompletion private var __synchronous:Bool;
|
||||
@:noCompletion private var __workCompleted:Int;
|
||||
@:noCompletion private var __workIncoming = new Deque<ThreadPoolMessage>();
|
||||
@:noCompletion private var __workQueued:Int;
|
||||
@:noCompletion private var __workResult = new Deque<ThreadPoolMessage>();
|
||||
Unavailable in Haxe 3 as thread equality checking doesn't work there.
|
||||
**/
|
||||
private static var __mainThread:Thread =
|
||||
#if html5
|
||||
!Thread.current().isWorker() ? Thread.current() : null;
|
||||
#else
|
||||
Thread.current();
|
||||
#end
|
||||
#end
|
||||
|
||||
public function new(minThreads:Int = 0, maxThreads:Int = 1)
|
||||
/**
|
||||
Indicates that no further events will be dispatched.
|
||||
**/
|
||||
public var canceled(default, null):Bool = false;
|
||||
|
||||
/**
|
||||
Indicates that the latest job finished successfully, and no other job
|
||||
has been started/is ongoing.
|
||||
**/
|
||||
public var completed(default, null):Bool = false;
|
||||
|
||||
/**
|
||||
The number of live threads in this pool, including both active and idle
|
||||
threads. Does not count threads that have been instructed to shut down.
|
||||
|
||||
In single-threaded mode, this will equal `activeJobs`.
|
||||
**/
|
||||
public var currentThreads(get, never):Int;
|
||||
|
||||
/**
|
||||
The number of jobs actively being executed.
|
||||
**/
|
||||
public var activeJobs(get, never):Int;
|
||||
|
||||
/**
|
||||
The number of live threads in this pool that aren't currently working on
|
||||
anything. In single-threaded mode, this will always be 0.
|
||||
**/
|
||||
public var idleThreads(get, never):Int;
|
||||
|
||||
/**
|
||||
__Set this only from the main thread.__
|
||||
|
||||
The maximum number of live threads this pool can have at once. If this
|
||||
value decreases, active jobs will still be allowed to finish.
|
||||
|
||||
You can set this in single-threaded mode, but it's rarely useful. For
|
||||
instance, suppose you have six jobs, each of which takes about a second.
|
||||
If you leave `maxThreads` at 1, then one will finish every second for
|
||||
six seconds. If you set `maxThreads = 6`, then none will finish for five
|
||||
seconds, and then they'll all finish at once. The total duration is
|
||||
unchanged, but none of them finish early.
|
||||
**/
|
||||
public var maxThreads:Int;
|
||||
|
||||
/**
|
||||
__Set this only from the main thread.__
|
||||
|
||||
The number of threads that will be kept alive at all times, even if
|
||||
there's no work to do. Setting this won't add new threads, it'll just
|
||||
keep existing ones running.
|
||||
|
||||
Has no effect in single-threaded mode.
|
||||
**/
|
||||
public var minThreads:Int;
|
||||
|
||||
/**
|
||||
Dispatched on the main thread when `doWork` calls `sendComplete()`.
|
||||
Dispatched at most once per job.
|
||||
**/
|
||||
public var onComplete(default, null) = new Event<Dynamic->Void>();
|
||||
/**
|
||||
Dispatched on the main thread when `doWork` calls `sendError()`.
|
||||
Dispatched at most once per job.
|
||||
**/
|
||||
public var onError(default, null) = new Event<Dynamic->Void>();
|
||||
/**
|
||||
Dispatched on the main thread when `doWork` calls `sendProgress()`. May
|
||||
be dispatched any number of times per job.
|
||||
**/
|
||||
public var onProgress(default, null) = new Event<Dynamic->Void>();
|
||||
/**
|
||||
Dispatched on the main thread when a new job begins. Dispatched exactly
|
||||
once per job.
|
||||
**/
|
||||
public var onRun(default, null) = new Event<State->Void>();
|
||||
|
||||
@:deprecated("Instead pass the callback to ThreadPool.run().")
|
||||
@:noCompletion @:dox(hide) public var doWork(get, never):PseudoEvent;
|
||||
private var __doWork:WorkFunction<State->WorkOutput->Void>;
|
||||
|
||||
private var __activeJobs:JobList = new JobList();
|
||||
|
||||
#if lime_threads
|
||||
/**
|
||||
The set of threads actively running a job.
|
||||
**/
|
||||
private var __activeThreads:Map<Int, Thread> = new Map();
|
||||
|
||||
/**
|
||||
A list of idle threads. Not to be confused with `idleThreads`, a public
|
||||
variable equal to `__idleThreads.length`.
|
||||
**/
|
||||
private var __idleThreads:List<Thread> = new List();
|
||||
#end
|
||||
|
||||
private var __jobQueue:JobList = new JobList();
|
||||
|
||||
private var __workPerFrame:Float;
|
||||
|
||||
/**
|
||||
__Call this only from the main thread.__
|
||||
|
||||
@param minThreads The number of threads that will be kept alive at all
|
||||
times, even if there's no work to do. The threads won't spin up
|
||||
immediately; only after enough calls to `run()`. Only applies in
|
||||
multi-threaded mode.
|
||||
@param maxThreads The maximum number of threads that will run at once.
|
||||
@param mode Defaults to `MULTI_THREADED` on most targets, but
|
||||
`SINGLE_THREADED` in HTML5. In HTML5, `MULTI_THREADED` mode uses web
|
||||
workers, which impose additional restrictions.
|
||||
@param workLoad (Single-threaded mode only) A rough estimate of how much
|
||||
of the app's time should be spent on this `ThreadPool`. For instance,
|
||||
the default value of 1/2 means this worker will take up about half the
|
||||
app's available time every frame. See `workIterations` for instructions
|
||||
to improve the accuracy of this estimate.
|
||||
**/
|
||||
public function new(minThreads:Int = 0, maxThreads:Int = 1, mode:ThreadMode = null, workLoad:Float = 1/2)
|
||||
{
|
||||
this.minThreads = minThreads;
|
||||
this.maxThreads = maxThreads;
|
||||
super(mode);
|
||||
|
||||
currentThreads = 0;
|
||||
|
||||
#if (cpp || neko || webassembly)
|
||||
__workQueued = 0;
|
||||
__workCompleted = 0;
|
||||
#end
|
||||
|
||||
#if (webassembly || force_synchronous)
|
||||
__synchronous = true;
|
||||
#end
|
||||
}
|
||||
|
||||
// public function cancel (id:String):Void {
|
||||
//
|
||||
//
|
||||
//
|
||||
// }
|
||||
// public function isCanceled (id:String):Bool {
|
||||
//
|
||||
//
|
||||
//
|
||||
// }
|
||||
public function queue(state:Dynamic = null):Void
|
||||
{
|
||||
#if (cpp || neko || webassembly)
|
||||
// TODO: Better way to handle this?
|
||||
|
||||
if (Application.current != null && Application.current.window != null && !__synchronous)
|
||||
if (Application.current != null && Application.current.window != null)
|
||||
{
|
||||
__workIncoming.add(new ThreadPoolMessage(WORK, state));
|
||||
__workQueued++;
|
||||
|
||||
if (currentThreads < maxThreads && currentThreads < (__workQueued - __workCompleted))
|
||||
{
|
||||
currentThreads++;
|
||||
Thread.create(__doWork);
|
||||
}
|
||||
|
||||
if (!Application.current.onUpdate.has(__update))
|
||||
{
|
||||
Application.current.onUpdate.add(__update);
|
||||
}
|
||||
__workPerFrame = workLoad / Application.current.window.frameRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
__synchronous = true;
|
||||
runWork(state);
|
||||
__workPerFrame = workLoad / 60;
|
||||
}
|
||||
#else
|
||||
runWork(state);
|
||||
#end
|
||||
|
||||
this.minThreads = minThreads;
|
||||
this.maxThreads = maxThreads;
|
||||
}
|
||||
|
||||
public function sendComplete(state:Dynamic = null):Void
|
||||
/**
|
||||
Cancels all active and queued jobs. In multi-threaded mode, leaves
|
||||
`minThreads` idle threads running.
|
||||
@param error If not null, this error will be dispatched for each active
|
||||
or queued job.
|
||||
**/
|
||||
public function cancel(error:Dynamic = null):Void
|
||||
{
|
||||
#if (cpp || neko || webassembly)
|
||||
if (!__synchronous)
|
||||
#if (haxe4 && lime_threads)
|
||||
if (Thread.current() != __mainThread)
|
||||
{
|
||||
__workResult.add(new ThreadPoolMessage(COMPLETE, state));
|
||||
return;
|
||||
throw "Call cancel() only from the main thread.";
|
||||
}
|
||||
#end
|
||||
|
||||
onComplete.dispatch(state);
|
||||
}
|
||||
Application.current.onUpdate.remove(__update);
|
||||
|
||||
public function sendError(state:Dynamic = null):Void
|
||||
{
|
||||
#if (cpp || neko || webassembly)
|
||||
if (!__synchronous)
|
||||
// Cancel active jobs, leaving `minThreads` idle threads.
|
||||
for (job in __activeJobs)
|
||||
{
|
||||
__workResult.add(new ThreadPoolMessage(ERROR, state));
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
onError.dispatch(state);
|
||||
}
|
||||
|
||||
public function sendProgress(state:Dynamic = null):Void
|
||||
{
|
||||
#if (cpp || neko || webassembly)
|
||||
if (!__synchronous)
|
||||
{
|
||||
__workResult.add(new ThreadPoolMessage(PROGRESS, state));
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
onProgress.dispatch(state);
|
||||
}
|
||||
|
||||
@:noCompletion private function runWork(state:Dynamic = null):Void
|
||||
{
|
||||
#if (cpp || neko || webassembly)
|
||||
if (!__synchronous)
|
||||
{
|
||||
__workResult.add(new ThreadPoolMessage(WORK, state));
|
||||
doWork.dispatch(state);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
onRun.dispatch(state);
|
||||
doWork.dispatch(state);
|
||||
}
|
||||
|
||||
#if (cpp || neko || webassembly)
|
||||
@:noCompletion private function __doWork():Void
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var message = __workIncoming.pop(true);
|
||||
|
||||
if (message.type == WORK)
|
||||
#if lime_threads
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
runWork(message.state);
|
||||
}
|
||||
else if (message.type == EXIT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:noCompletion private function __update(deltaTime:Int):Void
|
||||
{
|
||||
if (__workQueued > __workCompleted)
|
||||
{
|
||||
var message = __workResult.pop(false);
|
||||
|
||||
while (message != null)
|
||||
{
|
||||
switch (message.type)
|
||||
var thread:Thread = __activeThreads[job.id];
|
||||
if (idleThreads < minThreads)
|
||||
{
|
||||
case WORK:
|
||||
onRun.dispatch(message.state);
|
||||
thread.sendMessage(new ThreadEvent(WORK, null, null));
|
||||
__idleThreads.push(thread);
|
||||
}
|
||||
else
|
||||
{
|
||||
thread.sendMessage(new ThreadEvent(EXIT, null, null));
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
case PROGRESS:
|
||||
onProgress.dispatch(message.state);
|
||||
if (error != null)
|
||||
{
|
||||
if (job.duration == 0)
|
||||
{
|
||||
job.duration = timestamp() - job.startTime;
|
||||
}
|
||||
|
||||
case COMPLETE, ERROR:
|
||||
__workCompleted++;
|
||||
activeJob = job;
|
||||
onError.dispatch(error);
|
||||
activeJob = null;
|
||||
}
|
||||
}
|
||||
__activeJobs.clear();
|
||||
|
||||
if ((currentThreads > (__workQueued - __workCompleted) && currentThreads > minThreads)
|
||||
|| currentThreads > maxThreads)
|
||||
#if lime_threads
|
||||
// Cancel idle threads if there are more than the minimum.
|
||||
while (idleThreads > minThreads)
|
||||
{
|
||||
__idleThreads.pop().sendMessage(new ThreadEvent(EXIT, null, null));
|
||||
}
|
||||
#end
|
||||
|
||||
// Clear the job queue.
|
||||
if (error != null)
|
||||
{
|
||||
for (job in __jobQueue)
|
||||
{
|
||||
activeJob = job;
|
||||
onError.dispatch(error);
|
||||
}
|
||||
}
|
||||
__jobQueue.clear();
|
||||
|
||||
__jobComplete.value = false;
|
||||
activeJob = null;
|
||||
completed = false;
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
Cancels one active or queued job. Does not dispatch an error event.
|
||||
@param job A `JobData` object, or a job's unique `id`, `state`, or
|
||||
`doWork` function.
|
||||
@return Whether a job was canceled.
|
||||
**/
|
||||
public function cancelJob(job:JobIdentifier):Bool
|
||||
{
|
||||
var data:JobData = __activeJobs.get(job);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
#if lime_threads
|
||||
var thread:Thread = __activeThreads[data.id];
|
||||
if (thread != null)
|
||||
{
|
||||
thread.sendMessage(new ThreadEvent(WORK, null, null));
|
||||
__activeThreads.remove(data.id);
|
||||
__idleThreads.push(thread);
|
||||
}
|
||||
#end
|
||||
|
||||
return __activeJobs.remove(data);
|
||||
}
|
||||
|
||||
return __jobQueue.remove(__jobQueue.get(job));
|
||||
}
|
||||
|
||||
/**
|
||||
Alias for `ThreadPool.run()`.
|
||||
**/
|
||||
@:noCompletion public inline function queue(doWork:WorkFunction<State->WorkOutput->Void> = null, state:State = null):Int
|
||||
{
|
||||
return run(doWork, state);
|
||||
}
|
||||
|
||||
/**
|
||||
Queues a new job, to be run once a thread becomes available.
|
||||
@return The job's unique ID.
|
||||
**/
|
||||
public function run(doWork:WorkFunction<State->WorkOutput->Void> = null, state:State = null):Int
|
||||
{
|
||||
#if (haxe4 && lime_threads)
|
||||
if (Thread.current() != __mainThread)
|
||||
{
|
||||
throw "Call run() only from the main thread.";
|
||||
}
|
||||
#end
|
||||
|
||||
if (doWork == null)
|
||||
{
|
||||
if (__doWork == null)
|
||||
{
|
||||
throw "run() requires doWork argument.";
|
||||
}
|
||||
else
|
||||
{
|
||||
doWork = __doWork;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
state = {};
|
||||
}
|
||||
|
||||
var job:JobData = new JobData(doWork, state);
|
||||
__jobQueue.add(job);
|
||||
completed = false;
|
||||
canceled = false;
|
||||
|
||||
if (Application.current != null && !Application.current.onUpdate.has(__update))
|
||||
{
|
||||
Application.current.onUpdate.add(__update);
|
||||
}
|
||||
|
||||
return job.id;
|
||||
}
|
||||
|
||||
#if lime_threads
|
||||
/**
|
||||
__Run this only on a background thread.__
|
||||
|
||||
Retrieves jobs using `Thread.readMessage()`, runs them until complete,
|
||||
and repeats.
|
||||
|
||||
On all targets besides HTML5, the first message must be a `WorkOutput`.
|
||||
**/
|
||||
private static function __executeThread():Void
|
||||
{
|
||||
JSAsync.async({
|
||||
var output:WorkOutput = #if html5 new WorkOutput(MULTI_THREADED) #else cast(Thread.readMessage(true), WorkOutput) #end;
|
||||
var event:ThreadEvent = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Get a job.
|
||||
if (event == null)
|
||||
{
|
||||
do
|
||||
{
|
||||
event = Thread.readMessage(true);
|
||||
}
|
||||
while (!#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (event, ThreadEvent));
|
||||
|
||||
output.resetJobProgress();
|
||||
}
|
||||
|
||||
if (event.event == EXIT)
|
||||
{
|
||||
// Quit working.
|
||||
#if html5
|
||||
Thread.current().destroy();
|
||||
#end
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.event != WORK || event.job == null)
|
||||
{
|
||||
// Go idle.
|
||||
event = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get to work.
|
||||
output.activeJob = event.job;
|
||||
|
||||
var interruption:Dynamic = null;
|
||||
try
|
||||
{
|
||||
while (!output.__jobComplete.value && (interruption = Thread.readMessage(false)) == null)
|
||||
{
|
||||
output.workIterations.value++;
|
||||
event.job.doWork.dispatch(event.job.state, output);
|
||||
}
|
||||
}
|
||||
catch (e:#if (haxe_ver >= 4.1) haxe.Exception #else Dynamic #end)
|
||||
{
|
||||
output.sendError(e);
|
||||
}
|
||||
|
||||
output.activeJob = null;
|
||||
|
||||
if (interruption == null || output.__jobComplete.value)
|
||||
{
|
||||
// Work is done; wait for more.
|
||||
event = null;
|
||||
}
|
||||
else if(#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (interruption, ThreadEvent))
|
||||
{
|
||||
// Work on the new job.
|
||||
event = interruption;
|
||||
output.resetJobProgress();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore interruption and keep working.
|
||||
}
|
||||
|
||||
// Do it all again.
|
||||
}
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
private static inline function timestamp():Float
|
||||
{
|
||||
#if sys
|
||||
return Sys.cpuTime();
|
||||
#else
|
||||
return haxe.Timer.stamp();
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Schedules (in multi-threaded mode) or runs (in single-threaded mode) the
|
||||
job queue, then processes incoming events.
|
||||
**/
|
||||
private function __update(deltaTime:Int):Void
|
||||
{
|
||||
#if (haxe4 && lime_threads)
|
||||
if (Thread.current() != __mainThread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
// Process the queue.
|
||||
while (!__jobQueue.isEmpty() && activeJobs < maxThreads)
|
||||
{
|
||||
var job:JobData = __jobQueue.pop();
|
||||
|
||||
job.startTime = timestamp();
|
||||
__activeJobs.push(job);
|
||||
|
||||
#if lime_threads
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
#if html5
|
||||
job.doWork.makePortable();
|
||||
#end
|
||||
|
||||
var thread:Thread = __idleThreads.isEmpty() ? createThread(__executeThread) : __idleThreads.pop();
|
||||
__activeThreads[job.id] = thread;
|
||||
thread.sendMessage(new ThreadEvent(WORK, null, job));
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
// Run the next single-threaded job.
|
||||
if (mode == SINGLE_THREADED && activeJobs > 0)
|
||||
{
|
||||
activeJob = __activeJobs.pop();
|
||||
var state:State = activeJob.state;
|
||||
|
||||
__jobComplete.value = false;
|
||||
workIterations.value = 0;
|
||||
|
||||
var startTime:Float = timestamp();
|
||||
var timeElapsed:Float = 0;
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
workIterations.value++;
|
||||
activeJob.doWork.dispatch(state, this);
|
||||
timeElapsed = timestamp() - startTime;
|
||||
}
|
||||
while (!__jobComplete.value && timeElapsed < __workPerFrame);
|
||||
}
|
||||
catch (e:#if (haxe_ver >= 4.1) haxe.Exception #else Dynamic #end)
|
||||
{
|
||||
sendError(e);
|
||||
}
|
||||
|
||||
activeJob.duration += timeElapsed;
|
||||
|
||||
// Add this job to the end of the list, to cycle through.
|
||||
__activeJobs.add(activeJob);
|
||||
|
||||
activeJob = null;
|
||||
}
|
||||
|
||||
var threadEvent:ThreadEvent;
|
||||
while ((threadEvent = __jobOutput.pop(false)) != null)
|
||||
{
|
||||
if (!__activeJobs.exists(threadEvent.job))
|
||||
{
|
||||
// Ignore events from canceled jobs.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 (mode == MULTI_THREADED)
|
||||
{
|
||||
activeJob.duration = timestamp() - activeJob.startTime;
|
||||
}
|
||||
|
||||
switch (threadEvent.event)
|
||||
{
|
||||
case WORK:
|
||||
onRun.dispatch(threadEvent.message);
|
||||
|
||||
case PROGRESS:
|
||||
onProgress.dispatch(threadEvent.message);
|
||||
|
||||
case COMPLETE, ERROR:
|
||||
if (threadEvent.event == COMPLETE)
|
||||
{
|
||||
onComplete.dispatch(threadEvent.message);
|
||||
}
|
||||
else
|
||||
{
|
||||
onError.dispatch(threadEvent.message);
|
||||
}
|
||||
|
||||
__activeJobs.remove(activeJob);
|
||||
|
||||
#if lime_threads
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
var thread:Thread = __activeThreads[activeJob.id];
|
||||
__activeThreads.remove(activeJob.id);
|
||||
|
||||
if (currentThreads > maxThreads || __jobQueue.isEmpty() && currentThreads > minThreads)
|
||||
{
|
||||
currentThreads--;
|
||||
__workIncoming.add(new ThreadPoolMessage(EXIT, null));
|
||||
}
|
||||
|
||||
if (message.type == COMPLETE)
|
||||
{
|
||||
onComplete.dispatch(message.state);
|
||||
thread.sendMessage(new ThreadEvent(EXIT, null, null));
|
||||
}
|
||||
else
|
||||
{
|
||||
onError.dispatch(message.state);
|
||||
__idleThreads.push(thread);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
default:
|
||||
}
|
||||
completed = threadEvent.event == COMPLETE && activeJobs == 0 && __jobQueue.isEmpty();
|
||||
|
||||
message = __workResult.pop(false);
|
||||
default:
|
||||
}
|
||||
|
||||
activeJob = null;
|
||||
}
|
||||
else
|
||||
|
||||
if (completed && Application.current != null)
|
||||
{
|
||||
// TODO: Add sleep if keeping minThreads running with no work?
|
||||
|
||||
if (currentThreads == 0 && minThreads <= 0 && Application.current != null)
|
||||
{
|
||||
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
|
||||
|
||||
return thread;
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
private enum ThreadPoolMessageType
|
||||
{
|
||||
COMPLETE;
|
||||
ERROR;
|
||||
EXIT;
|
||||
PROGRESS;
|
||||
WORK;
|
||||
}
|
||||
// Getters & Setters
|
||||
|
||||
private class ThreadPoolMessage
|
||||
{
|
||||
public var state:Dynamic;
|
||||
public var type:ThreadPoolMessageType;
|
||||
|
||||
public function new(type:ThreadPoolMessageType, state:Dynamic)
|
||||
private inline function get_activeJobs():Int
|
||||
{
|
||||
this.type = type;
|
||||
this.state = state;
|
||||
return __activeJobs.length;
|
||||
}
|
||||
|
||||
private inline function get_idleThreads():Int
|
||||
{
|
||||
return #if lime_threads __idleThreads.length #else 0 #end;
|
||||
}
|
||||
|
||||
private inline function get_currentThreads():Int
|
||||
{
|
||||
return activeJobs + idleThreads;
|
||||
}
|
||||
|
||||
// Note the distinction between `doWork` and `__doWork`: the former is for
|
||||
// backwards compatibility, while the latter is always used.
|
||||
private function get_doWork():PseudoEvent
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@:access(lime.system.ThreadPool) @:forward(canceled)
|
||||
private abstract PseudoEvent(ThreadPool) from ThreadPool {
|
||||
@:noCompletion @:dox(hide) public var __listeners(get, never):Array<Dynamic>;
|
||||
private inline function get___listeners():Array<Dynamic> { return []; };
|
||||
@:noCompletion @:dox(hide) public var __repeat(get, never):Array<Bool>;
|
||||
private inline function get___repeat():Array<Bool> { return []; };
|
||||
|
||||
public function add(callback:Dynamic -> Void):Void {
|
||||
function callCallback(state:State, output:WorkOutput):Void
|
||||
{
|
||||
callback(state);
|
||||
}
|
||||
|
||||
#if (lime_threads && html5)
|
||||
if (this.mode == MULTI_THREADED)
|
||||
throw "Unsupported operation; instead pass the callback to ThreadPool's constructor.";
|
||||
else
|
||||
this.__doWork = { func: callCallback };
|
||||
#else
|
||||
this.__doWork = callCallback;
|
||||
#end
|
||||
}
|
||||
|
||||
public inline function cancel():Void {}
|
||||
public inline function dispatch():Void {}
|
||||
public inline function has(callback:Dynamic -> Void):Bool { return this.__doWork != null; }
|
||||
public inline function remove(callback:Dynamic -> Void):Void { this.__doWork = null; }
|
||||
public inline function removeAll():Void { this.__doWork = null; }
|
||||
}
|
||||
|
||||
@:forward
|
||||
abstract JobList(List<JobData>)
|
||||
{
|
||||
public inline function new()
|
||||
{
|
||||
this = new List<JobData>();
|
||||
}
|
||||
|
||||
public inline function exists(job:JobData):Bool
|
||||
{
|
||||
return getByID(job.id) != null;
|
||||
}
|
||||
|
||||
public inline function remove(job:JobData):Bool
|
||||
{
|
||||
return this.remove(job) || removeByID(job.id);
|
||||
}
|
||||
|
||||
public inline function removeByID(id:Int):Bool
|
||||
{
|
||||
return this.remove(getByID(id));
|
||||
}
|
||||
|
||||
public function getByID(id:Int):JobData
|
||||
{
|
||||
for (job in this)
|
||||
{
|
||||
if (job.id == id)
|
||||
{
|
||||
return job;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get(jobIdentifier:JobIdentifier):JobData
|
||||
{
|
||||
switch (jobIdentifier)
|
||||
{
|
||||
case ID(id):
|
||||
return getByID(id);
|
||||
case FUNCTION(doWork):
|
||||
for (job in this)
|
||||
{
|
||||
if (job.doWork == doWork)
|
||||
{
|
||||
return job;
|
||||
}
|
||||
}
|
||||
case STATE(state):
|
||||
for (job in this)
|
||||
{
|
||||
if (job.state == state)
|
||||
{
|
||||
return job;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A piece of data that uniquely represents a job. This can be the integer ID
|
||||
(and integers will be assumed to be such), the `doWork` function, or the
|
||||
`JobData` object itself. Failing any of those, a value will be assumed to be
|
||||
the job's `state`.
|
||||
|
||||
Caution: if the provided data isn't unique, such as a `doWork` function
|
||||
that's in use by multiple jobs, the wrong job may be selected or canceled.
|
||||
**/
|
||||
@:forward
|
||||
abstract JobIdentifier(JobIdentifierImpl) from JobIdentifierImpl {
|
||||
@:from private static inline function fromJob(job:JobData):JobIdentifier {
|
||||
return ID(job.id);
|
||||
}
|
||||
@:from private static inline function fromID(id:Int):JobIdentifier {
|
||||
return ID(id);
|
||||
}
|
||||
@:from private static inline function fromFunction(doWork:WorkFunction<State->WorkOutput->Void>):JobIdentifier {
|
||||
return FUNCTION(doWork);
|
||||
}
|
||||
@:from private static inline function fromState(state:State):JobIdentifier {
|
||||
return STATE(state);
|
||||
}
|
||||
}
|
||||
|
||||
private enum JobIdentifierImpl
|
||||
{
|
||||
ID(id:Int);
|
||||
FUNCTION(doWork:WorkFunction<State->WorkOutput->Void>);
|
||||
STATE(state:State);
|
||||
}
|
||||
|
||||
434
src/lime/system/WorkOutput.hx
Normal file
434
src/lime/system/WorkOutput.hx
Normal file
@@ -0,0 +1,434 @@
|
||||
package lime.system;
|
||||
|
||||
#if target.threaded
|
||||
import sys.thread.Deque;
|
||||
import sys.thread.Thread;
|
||||
import sys.thread.Tls;
|
||||
#elseif cpp
|
||||
import cpp.vm.Deque;
|
||||
import cpp.vm.Thread;
|
||||
import cpp.vm.Tls;
|
||||
#elseif neko
|
||||
import neko.vm.Deque;
|
||||
import neko.vm.Thread;
|
||||
import neko.vm.Tls;
|
||||
#end
|
||||
|
||||
#if html5
|
||||
import lime._internal.backend.html5.HTML5Thread as Thread;
|
||||
import lime._internal.backend.html5.HTML5Thread.Transferable;
|
||||
#end
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using haxe.macro.Context;
|
||||
#end
|
||||
|
||||
// In addition to `WorkOutput`, this module contains a number of small enums,
|
||||
// abstracts, and classes used by all of Lime's threading classes.
|
||||
|
||||
/**
|
||||
Functions and variables available to the `doWork` function. For instance,
|
||||
the `sendProgress()`, `sendComplete()`, and `sendError()` functions allow
|
||||
returning output.
|
||||
|
||||
`doWork` should exclusively use `WorkOutput` to communicate with the main
|
||||
thread. On many targets it's also possible to access static or instance
|
||||
variables, but this isn't thread safe and won't work in HTML5.
|
||||
**/
|
||||
class WorkOutput
|
||||
{
|
||||
/**
|
||||
Thread-local storage. Tracks how many times `doWork` has been called for
|
||||
the current job, including (if applicable) the ongoing call.
|
||||
|
||||
In single-threaded mode, it only counts the number of calls this frame.
|
||||
This helps you adjust `doWork`'s length: too few iterations per frame
|
||||
means `workLoad` may be inaccurate, while too many may add overhead.
|
||||
**/
|
||||
public var workIterations(default, null):Tls<Int> = new Tls();
|
||||
|
||||
/**
|
||||
Whether background threads are being/will be used. If threads aren't
|
||||
available on this target, `mode` will always be `SINGLE_THREADED`.
|
||||
**/
|
||||
public var mode(get, never):ThreadMode;
|
||||
#if lime_threads
|
||||
/**
|
||||
__Set this only via the constructor.__
|
||||
**/
|
||||
private var __mode:ThreadMode;
|
||||
#end
|
||||
|
||||
/**
|
||||
Messages sent by active jobs, received by the main thread.
|
||||
**/
|
||||
private var __jobOutput:Deque<ThreadEvent> = new Deque();
|
||||
/**
|
||||
Thread-local storage. Tracks whether `sendError()` or `sendComplete()`
|
||||
was called by this job.
|
||||
**/
|
||||
private var __jobComplete:Tls<Bool> = new Tls();
|
||||
|
||||
/**
|
||||
The job that is currently running on this thread, or the job that
|
||||
triggered the ongoing `onComplete`, `onError`, or `onProgress` event.
|
||||
Will be null in all other cases.
|
||||
**/
|
||||
public var activeJob(get, set):Null<JobData>;
|
||||
@:noCompletion private var __activeJob:Tls<JobData> = new Tls();
|
||||
|
||||
private inline function new(mode:Null<ThreadMode>)
|
||||
{
|
||||
workIterations.value = 0;
|
||||
__jobComplete.value = false;
|
||||
|
||||
#if lime_threads
|
||||
__mode = mode != null ? mode : #if html5 SINGLE_THREADED #else MULTI_THREADED #end;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
Dispatches `onComplete` on the main thread, with the given message.
|
||||
`doWork` should return after calling this.
|
||||
|
||||
If using web workers, you can also pass a list of transferable objects.
|
||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||
**/
|
||||
public function sendComplete(message:Dynamic = null, transferList:Array<Transferable> = null):Void
|
||||
{
|
||||
if (!__jobComplete.value)
|
||||
{
|
||||
__jobComplete.value = true;
|
||||
|
||||
#if (lime_threads && html5)
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
activeJob.doWork.makePortable();
|
||||
Thread.returnMessage(new ThreadEvent(COMPLETE, message, activeJob), transferList);
|
||||
}
|
||||
else
|
||||
#end
|
||||
__jobOutput.add(new ThreadEvent(COMPLETE, message, activeJob));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Dispatches `onError` on the main thread, with the given message.
|
||||
`doWork` should return after calling this.
|
||||
|
||||
If using web workers, you can also pass a list of transferable objects.
|
||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||
**/
|
||||
public function sendError(message:Dynamic = null, transferList:Array<Transferable> = null):Void
|
||||
{
|
||||
if (!__jobComplete.value)
|
||||
{
|
||||
__jobComplete.value = true;
|
||||
|
||||
#if (lime_threads && html5)
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
activeJob.doWork.makePortable();
|
||||
Thread.returnMessage(new ThreadEvent(ERROR, message, activeJob), transferList);
|
||||
}
|
||||
else
|
||||
#end
|
||||
__jobOutput.add(new ThreadEvent(ERROR, message, activeJob));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Dispatches `onProgress` on the main thread, with the given message. This
|
||||
can be called any number of times per job.
|
||||
|
||||
If using web workers, you can also pass a list of transferable objects.
|
||||
@see https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects
|
||||
**/
|
||||
public function sendProgress(message:Dynamic = null, transferList:Array<Transferable> = null):Void
|
||||
{
|
||||
if (!__jobComplete.value)
|
||||
{
|
||||
#if (lime_threads && html5)
|
||||
if (mode == MULTI_THREADED)
|
||||
{
|
||||
activeJob.doWork.makePortable();
|
||||
Thread.returnMessage(new ThreadEvent(PROGRESS, message, activeJob), transferList);
|
||||
}
|
||||
else
|
||||
#end
|
||||
__jobOutput.add(new ThreadEvent(PROGRESS, message, activeJob));
|
||||
}
|
||||
}
|
||||
|
||||
private inline function resetJobProgress():Void
|
||||
{
|
||||
__jobComplete.value = false;
|
||||
workIterations.value = 0;
|
||||
}
|
||||
|
||||
#if lime_threads
|
||||
private function createThread(executeThread:WorkFunction<Void->Void>):Thread
|
||||
{
|
||||
var thread:Thread = Thread.create(executeThread);
|
||||
|
||||
#if html5
|
||||
thread.onMessage.add(function(event:ThreadEvent) {
|
||||
__jobOutput.add(event);
|
||||
});
|
||||
#end
|
||||
|
||||
return thread;
|
||||
}
|
||||
#end
|
||||
|
||||
// Getters & Setters
|
||||
|
||||
private inline function get_mode():ThreadMode
|
||||
{
|
||||
#if lime_threads
|
||||
return __mode;
|
||||
#else
|
||||
return SINGLE_THREADED;
|
||||
#end
|
||||
}
|
||||
|
||||
private inline function get_activeJob():JobData
|
||||
{
|
||||
return __activeJob.value;
|
||||
}
|
||||
private inline function set_activeJob(value:JobData):JobData
|
||||
{
|
||||
return __activeJob.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
#if haxe4 enum #else @:enum #end abstract ThreadMode(Bool)
|
||||
{
|
||||
/**
|
||||
All work will be done on the main thread, during `Application.onUpdate`.
|
||||
|
||||
To avoid lag spikes, `doWork` should return after completing a fraction
|
||||
of a frame's worth of work, storing its progress in `state`. It will be
|
||||
called again with the same `state` next frame, or this frame if there's
|
||||
still time.
|
||||
|
||||
@see https://en.wikipedia.org/wiki/Green_threads
|
||||
@see https://en.wikipedia.org/wiki/Cooperative_multitasking
|
||||
**/
|
||||
var SINGLE_THREADED = false;
|
||||
|
||||
/**
|
||||
All work will be done on a background thread.
|
||||
|
||||
Unlike single-threaded mode, there is no risk of causing lag spikes.
|
||||
Even so, `doWork` should return periodically, to allow canceling the
|
||||
thread. If not canceled, `doWork` will be called again immediately.
|
||||
|
||||
In HTML5, web workers will be used to achieve this. This means `doWork`
|
||||
must be a static function, and you can't use `bind()`. Web workers also
|
||||
impose a longer delay each time `doWork` returns, so it shouldn't return
|
||||
as often in multi-threaded mode as in single-threaded mode.
|
||||
**/
|
||||
var MULTI_THREADED = true;
|
||||
}
|
||||
|
||||
/**
|
||||
A function that performs asynchronous work. This can either be work on
|
||||
another thread ("multi-threaded mode"), or it can represent a virtual
|
||||
thread ("single-threaded mode").
|
||||
|
||||
In single-threaded mode, the work function shouldn't complete the job all at
|
||||
once, as the main thread would lock up. Instead, it should perform a
|
||||
fraction of the job each time it's called. `ThreadPool` provides the
|
||||
function with a persistent `State` argument that can track progress.
|
||||
Alternatively, you may be able to bind your own `State` argument.
|
||||
|
||||
Caution: if using multi-threaded mode in HTML5, this must be a static
|
||||
function and binding arguments is forbidden. Compile with
|
||||
`-Dlime-warn-portability` to highlight functions that won't work.
|
||||
|
||||
The exact length of `doWork` can vary, but single-threaded mode will run
|
||||
more smoothly if it's short enough to run several times per frame.
|
||||
**/
|
||||
#if (lime_threads && html5)
|
||||
typedef WorkFunction<T:haxe.Constraints.Function> = lime._internal.backend.html5.HTML5Thread.WorkFunction<T>;
|
||||
#else
|
||||
abstract WorkFunction<T:haxe.Constraints.Function>(T) from T to T
|
||||
{
|
||||
/**
|
||||
Executes this function with the given arguments.
|
||||
**/
|
||||
public macro function dispatch(self:Expr, args:Array<Expr>):Expr
|
||||
{
|
||||
switch (self.typeof().follow().toComplexType())
|
||||
{
|
||||
case TPath({ sub: "WorkFunction", params: [TPType(t)] }):
|
||||
return macro ($self:$t)($a{args});
|
||||
default:
|
||||
throw "Underlying function type not found.";
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
An argument of any type to pass to the `doWork` function. Though `doWork`
|
||||
only accepts a single argument, you can pass multiple values as part of an
|
||||
anonymous structure. (Or an array, or a class.)
|
||||
|
||||
// Does not work: too many arguments.
|
||||
// threadPool.run(doWork, argument0, argument1, argument2);
|
||||
|
||||
// Works: all arguments are combined into one `State` object.
|
||||
threadPool.run(doWork, { arg0: argument0, arg1: argument1, arg2: argument2 });
|
||||
|
||||
// Alternatives that also work, if everything is the correct type.
|
||||
threadPool.run(doWork, [argument0, argument1, argument2]);
|
||||
threadPool.run(doWork, new DoWorkArgs(argument0, argument1, argument2));
|
||||
|
||||
Any changes made to this object will persist if and when `doWork` is called
|
||||
again for the same job. (See `WorkFunction` for instructions on how to do
|
||||
this.) This is the recommended way to store `doWork`'s progress.
|
||||
|
||||
Caution: after passing an object to `doWork`, avoid accessing or modifying
|
||||
that object from the main thread, and avoid passing it to other threads.
|
||||
Doing either may lead to race conditions. If you need to store an object,
|
||||
pass a clone of that object to `doWork`.
|
||||
**/
|
||||
typedef State = Dynamic;
|
||||
|
||||
class JobData
|
||||
{
|
||||
private static var nextID:Int = 0;
|
||||
/**
|
||||
`JobData` instances will regularly be copied in HTML5, so checking
|
||||
equality won't work. Instead, compare identifiers.
|
||||
**/
|
||||
public var id(default, null):Int;
|
||||
|
||||
/**
|
||||
The function responsible for carrying out the job.
|
||||
**/
|
||||
public var doWork(default, null):WorkFunction<State->WorkOutput->Void>;
|
||||
|
||||
/**
|
||||
The original `State` object passed to the job. Avoid modifying this
|
||||
object if the job is running in multi-threaded mode.
|
||||
**/
|
||||
public var state(default, null):State;
|
||||
|
||||
/**
|
||||
The total time spent on this job.
|
||||
|
||||
In multi-threaded mode, this includes the overhead for sending messages,
|
||||
plus any time spent waiting for a canceled job to return. The latter
|
||||
delay can be reduced by returning at regular intervals.
|
||||
**/
|
||||
@:allow(lime.system.WorkOutput)
|
||||
public var duration(default, null):Float = 0;
|
||||
|
||||
@:allow(lime.system.WorkOutput)
|
||||
private var startTime:Float = 0;
|
||||
|
||||
@:allow(lime.system.WorkOutput)
|
||||
private inline function new(doWork:WorkFunction<State->WorkOutput->Void>, state:State)
|
||||
{
|
||||
id = nextID++;
|
||||
this.doWork = doWork;
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
#if haxe4 enum #else @:enum #end abstract ThreadEventType(String)
|
||||
{
|
||||
/**
|
||||
Sent by the background thread, indicating completion.
|
||||
**/
|
||||
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.)
|
||||
**/
|
||||
var WORK = "WORK";
|
||||
/**
|
||||
Sent by the main thread to shut down a thread.
|
||||
**/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define platform-specific types
|
||||
|
||||
#if target.threaded
|
||||
// Haxe 3 compatibility: "target.threaded" can't go in parentheses.
|
||||
#elseif !(cpp || neko)
|
||||
@:forward(push, add)
|
||||
abstract Deque<T>(List<T>) from List<T> to List<T>
|
||||
{
|
||||
public inline function new()
|
||||
{
|
||||
this = new List<T>();
|
||||
}
|
||||
|
||||
public inline function pop(block:Bool):Null<T>
|
||||
{
|
||||
return this.pop();
|
||||
}
|
||||
}
|
||||
|
||||
class Tls<T>
|
||||
{
|
||||
public var value:T;
|
||||
|
||||
public inline function new() {}
|
||||
}
|
||||
#end
|
||||
|
||||
#if !html5
|
||||
typedef Transferable = Dynamic;
|
||||
#end
|
||||
@@ -8,6 +8,7 @@ import lime.graphics.Image;
|
||||
import lime.graphics.ImageBuffer;
|
||||
import lime.math.Vector2;
|
||||
import lime.net.HTTPRequest;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.System;
|
||||
import lime.utils.Assets;
|
||||
import lime.utils.Log;
|
||||
@@ -474,11 +475,7 @@ class Font
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
#if hl
|
||||
name = @:privateAccess String.fromUTF8(NativeCFFI.lime_font_get_family_name(src));
|
||||
#else
|
||||
name = cast NativeCFFI.lime_font_get_family_name(src);
|
||||
#end
|
||||
name = CFFI.stringValue(cast NativeCFFI.lime_font_get_family_name(src));
|
||||
}
|
||||
|
||||
ascender = NativeCFFI.lime_font_get_ascender(src);
|
||||
|
||||
@@ -3,6 +3,7 @@ package lime.text.harfbuzz;
|
||||
#if (!lime_doc_gen || lime_harfbuzz)
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.math.Vector2;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.CFFIPointer;
|
||||
|
||||
@:access(lime._internal.backend.native.NativeCFFI)
|
||||
@@ -81,10 +82,7 @@ abstract HBFont(CFFIPointer) from CFFIPointer to CFFIPointer
|
||||
{
|
||||
#if (lime_cffi && lime_harfbuzz && !macro)
|
||||
var result = NativeCFFI.lime_hb_font_glyph_to_string(this, codepoint);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.text.harfbuzz;
|
||||
|
||||
#if (!lime_doc_gen || lime_harfbuzz)
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.CFFIPointer;
|
||||
|
||||
@:access(lime._internal.backend.native.NativeCFFI)
|
||||
@@ -29,10 +30,7 @@ abstract HBLanguage(CFFIPointer) from CFFIPointer to CFFIPointer
|
||||
if (this != null)
|
||||
{
|
||||
var result = NativeCFFI.lime_hb_language_to_string(this);
|
||||
#if hl
|
||||
var result = @:privateAccess String.fromUTF8(result);
|
||||
#end
|
||||
return result;
|
||||
return CFFI.stringValue(result);
|
||||
}
|
||||
#end
|
||||
return null;
|
||||
|
||||
@@ -9,6 +9,8 @@ import haxe.xml.Fast as Access;
|
||||
|
||||
abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
{
|
||||
private static inline var ARRAY:String = "___array";
|
||||
|
||||
public function new()
|
||||
{
|
||||
this = {};
|
||||
@@ -32,39 +34,12 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
public function exists(id:String):Bool
|
||||
{
|
||||
var tree = id.split('.');
|
||||
|
||||
if (tree.length <= 1)
|
||||
{
|
||||
return Reflect.hasField(this, id);
|
||||
}
|
||||
|
||||
var current = this;
|
||||
|
||||
for (leaf in tree)
|
||||
{
|
||||
if (Reflect.hasField(current, leaf))
|
||||
{
|
||||
current = Reflect.field(current, leaf);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return get(id) != null;
|
||||
}
|
||||
|
||||
public function get(id:String):ConfigData
|
||||
{
|
||||
var tree = id.split('.');
|
||||
|
||||
if (tree.length <= 1)
|
||||
{
|
||||
return Reflect.field(this, id);
|
||||
}
|
||||
|
||||
var tree = id.split(".");
|
||||
var current = this;
|
||||
|
||||
for (leaf in tree)
|
||||
@@ -82,41 +57,29 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
public function getArray(id:String, defaultValue:Array<Dynamic> = null):Array<Dynamic>
|
||||
{
|
||||
var tree = id.split('.');
|
||||
var tree = id.split(".");
|
||||
var array:Array<Dynamic> = null;
|
||||
|
||||
if (tree.length <= 1)
|
||||
{
|
||||
array = Reflect.field(this, id + "___array");
|
||||
var current = this;
|
||||
var field = tree.pop();
|
||||
|
||||
if (array == null && Reflect.hasField(this, id))
|
||||
for (leaf in tree)
|
||||
{
|
||||
current = Reflect.field(current, leaf);
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
array = [Reflect.field(this, id)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
var current = this;
|
||||
var field = tree.pop();
|
||||
array = Reflect.field(current, field + ARRAY);
|
||||
|
||||
for (leaf in tree)
|
||||
if (array == null && Reflect.hasField(current, field))
|
||||
{
|
||||
current = Reflect.field(current, leaf);
|
||||
|
||||
if (current == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
array = Reflect.field(current, field + "___array");
|
||||
|
||||
if (array == null && Reflect.hasField(current, field))
|
||||
{
|
||||
array = [Reflect.field(current, field)];
|
||||
}
|
||||
array = [Reflect.field(current, field)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +170,32 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public function getKeyValueArray(id:String, defaultValues:Dynamic = null):Array<{ key:Dynamic, value:Dynamic }>
|
||||
{
|
||||
var values = {};
|
||||
if (defaultValues != null)
|
||||
{
|
||||
ObjectTools.copyFields(defaultValues, values);
|
||||
}
|
||||
|
||||
var data = get(id);
|
||||
for (key in Reflect.fields(data))
|
||||
{
|
||||
if (!StringTools.endsWith (key, ARRAY))
|
||||
{
|
||||
Reflect.setField(values, key, Reflect.field(data, key));
|
||||
}
|
||||
}
|
||||
|
||||
var pairs = [];
|
||||
for (key in Reflect.fields(values))
|
||||
{
|
||||
pairs.push({ key: key, value: Reflect.field(values, key) });
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
private function log(v:Dynamic):Void
|
||||
{
|
||||
if (Log.verbose)
|
||||
@@ -227,7 +216,7 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
{
|
||||
for (field in Reflect.fields(source))
|
||||
{
|
||||
if (StringTools.endsWith(field, "___array"))
|
||||
if (StringTools.endsWith(field, ARRAY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -257,17 +246,17 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
if (doCopy && Reflect.field(source, field) != Reflect.field(destination, field) && typeSource != "TObject")
|
||||
{
|
||||
if (!Reflect.hasField(destination, field + "___array"))
|
||||
if (!Reflect.hasField(destination, field + ARRAY))
|
||||
{
|
||||
Reflect.setField(destination, field + "___array", [ObjectTools.deepCopy(Reflect.field(destination, field))]);
|
||||
Reflect.setField(destination, field + ARRAY, [ObjectTools.deepCopy(Reflect.field(destination, field))]);
|
||||
}
|
||||
|
||||
var array:Array<Dynamic> = Reflect.field(destination, field + "___array");
|
||||
var array:Array<Dynamic> = Reflect.field(destination, field + ARRAY);
|
||||
|
||||
if (Reflect.hasField(source, field + "___array"))
|
||||
if (Reflect.hasField(source, field + ARRAY))
|
||||
{
|
||||
array = array.concat(Reflect.field(source, field + "___array"));
|
||||
Reflect.setField(destination, field + "___array", array);
|
||||
array = array.concat(Reflect.field(source, field + ARRAY));
|
||||
Reflect.setField(destination, field + ARRAY, array);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -289,9 +278,9 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
{
|
||||
Reflect.setField(destination, field, Reflect.field(source, field));
|
||||
|
||||
if (Reflect.hasField(source, field + "___array"))
|
||||
if (Reflect.hasField(source, field + ARRAY))
|
||||
{
|
||||
Reflect.setField(destination, field + "___array", Reflect.field(source, field + "___array"));
|
||||
Reflect.setField(destination, field + ARRAY, Reflect.field(source, field + ARRAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,7 +294,7 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
if (StringTools.startsWith(elem.name, "config:"))
|
||||
{
|
||||
var items = elem.name.split(':');
|
||||
var items = elem.name.split(":");
|
||||
bucketType = items[1];
|
||||
}
|
||||
|
||||
@@ -353,12 +342,13 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
if (Reflect.hasField(bucket, child.name))
|
||||
{
|
||||
if (!Reflect.hasField(bucket, child.name + "___array"))
|
||||
var array:Array<Dynamic> = Reflect.field(bucket, child.name + ARRAY);
|
||||
if (array == null)
|
||||
{
|
||||
Reflect.setField(bucket, child.name + "___array", [ObjectTools.deepCopy(Reflect.field(bucket, child.name))]);
|
||||
array = [ObjectTools.deepCopy(Reflect.field(bucket, child.name))];
|
||||
Reflect.setField(bucket, child.name + ARRAY, array);
|
||||
}
|
||||
|
||||
var array:Array<Dynamic> = Reflect.field(bucket, child.name + "___array");
|
||||
var arrayBucket = {};
|
||||
array.push(arrayBucket);
|
||||
|
||||
@@ -410,27 +400,9 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
}
|
||||
}
|
||||
|
||||
public function push(id:String, value:Dynamic):Void
|
||||
public function push(id:String, value:Dynamic, ?unique:Bool = false):Void
|
||||
{
|
||||
var tree = id.split('.');
|
||||
|
||||
if (tree.length <= 1)
|
||||
{
|
||||
if (Reflect.hasField(this, id))
|
||||
{
|
||||
if (!Reflect.hasField(this, id + "___array"))
|
||||
{
|
||||
Reflect.setField(this, id + "___array", Reflect.hasField(this, id) ? [ObjectTools.deepCopy(Reflect.field(this, id))] : []);
|
||||
}
|
||||
|
||||
var array:Array<Dynamic> = Reflect.field(this, id + "___array");
|
||||
array.push(value);
|
||||
}
|
||||
|
||||
Reflect.setField(this, id, value);
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = id.split(".");
|
||||
var current = this;
|
||||
var field = tree.pop();
|
||||
|
||||
@@ -454,13 +426,18 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
if (Reflect.hasField(current, field))
|
||||
{
|
||||
if (!Reflect.hasField(current, field + "___array"))
|
||||
var array:Array<Dynamic> = Reflect.field(current, field + ARRAY);
|
||||
|
||||
if (array == null)
|
||||
{
|
||||
Reflect.setField(current, field + "___array", Reflect.hasField(current, field) ? [ObjectTools.deepCopy(Reflect.field(current, field))] : []);
|
||||
array = [ObjectTools.deepCopy(Reflect.field(current, field))];
|
||||
Reflect.setField(current, field + ARRAY, array);
|
||||
}
|
||||
|
||||
var array:Array<Dynamic> = Reflect.field(current, field + "___array");
|
||||
array.push(value);
|
||||
if (!unique || array.indexOf(value) == -1)
|
||||
{
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
Reflect.setField(current, field, value);
|
||||
@@ -468,14 +445,7 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
|
||||
public function set(id:String, value:Dynamic):Void
|
||||
{
|
||||
var tree = id.split('.');
|
||||
|
||||
if (tree.length <= 1)
|
||||
{
|
||||
Reflect.setField(this, id, value);
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = id.split(".");
|
||||
var current = this;
|
||||
var field = tree.pop();
|
||||
|
||||
@@ -525,12 +495,14 @@ abstract ConfigData(Dynamic) to Dynamic from Dynamic
|
||||
{
|
||||
if (typeSource != "TObject")
|
||||
{
|
||||
if (!Reflect.hasField(bucket, node + "___array"))
|
||||
var array:Array<Dynamic> = Reflect.field(bucket, node + ARRAY);
|
||||
if (array == null)
|
||||
{
|
||||
Reflect.setField(bucket, node + "___array", [ObjectTools.deepCopy(Reflect.field(bucket, node))]);
|
||||
array = [ObjectTools.deepCopy(Reflect.field(bucket, node))];
|
||||
Reflect.setField(bucket, node + ARRAY, array);
|
||||
}
|
||||
|
||||
cast(Reflect.field(bucket, node + "___array"), Array<Dynamic>).push(value);
|
||||
array.push(value);
|
||||
}
|
||||
|
||||
Reflect.setField(bucket, node, value);
|
||||
|
||||
@@ -5,6 +5,7 @@ class Dependency
|
||||
// TODO: Is "forceLoad" the best name? Implement "whole-archive" on GCC
|
||||
public var embed:Bool;
|
||||
public var forceLoad:Bool;
|
||||
public var webWorker:Bool;
|
||||
public var name:String;
|
||||
public var path:String;
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class HXProject extends Script
|
||||
public var templatePaths:Array<String>;
|
||||
@:isVar public var window(get, set):WindowData;
|
||||
public var windows:Array<WindowData>;
|
||||
public var projectFilePath:String;
|
||||
|
||||
private var needRerun:Bool;
|
||||
|
||||
@@ -86,7 +87,7 @@ class HXProject extends Script
|
||||
var outputFile = args[1];
|
||||
|
||||
HXProject._command = inputData.command;
|
||||
HXProject._target = cast inputData.target;
|
||||
HXProject._target = inputData.target;
|
||||
HXProject._debug = inputData.debug;
|
||||
HXProject._targetFlags = inputData.targetFlags;
|
||||
HXProject._templatePaths = inputData.templatePaths;
|
||||
@@ -101,9 +102,14 @@ class HXProject extends Script
|
||||
|
||||
Haxelib.debug = inputData.haxelibDebug;
|
||||
|
||||
initialize();
|
||||
initializeStatics();
|
||||
|
||||
var classRef = Type.resolveClass(inputData.name);
|
||||
if (classRef == null)
|
||||
{
|
||||
Log.error('Unable to find class ${ inputData.name } in ${ inputData.projectFile }');
|
||||
return;
|
||||
}
|
||||
var instance = Type.createInstance(classRef, []);
|
||||
|
||||
var serializer = new Serializer();
|
||||
@@ -113,11 +119,11 @@ class HXProject extends Script
|
||||
File.saveContent(outputFile, serializer.toString());
|
||||
}
|
||||
|
||||
public function new()
|
||||
public function new(defines:Map<String, Dynamic> = null)
|
||||
{
|
||||
super();
|
||||
|
||||
initialize();
|
||||
initializeStatics();
|
||||
|
||||
command = _command;
|
||||
config = new ConfigData();
|
||||
@@ -161,13 +167,17 @@ class HXProject extends Script
|
||||
windows = [window];
|
||||
assets = new Array<Asset>();
|
||||
|
||||
if (_userDefines != null)
|
||||
if (defines != null)
|
||||
{
|
||||
defines = MapTools.copy(_userDefines);
|
||||
this.defines = MapTools.copy(defines);
|
||||
}
|
||||
else if (_userDefines != null)
|
||||
{
|
||||
this.defines = MapTools.copy(_userDefines);
|
||||
}
|
||||
else
|
||||
{
|
||||
defines = new Map<String, String>();
|
||||
this.defines = new Map<String, String>();
|
||||
}
|
||||
|
||||
dependencies = new Array<Dependency>();
|
||||
@@ -197,6 +207,8 @@ class HXProject extends Script
|
||||
samplePaths = new Array<String>();
|
||||
splashScreens = new Array<SplashScreen>();
|
||||
targetHandlers = new Map<String, String>();
|
||||
|
||||
initializeDefines();
|
||||
}
|
||||
|
||||
public function clone():HXProject
|
||||
@@ -372,9 +384,9 @@ class HXProject extends Script
|
||||
|
||||
var path = FileSystem.fullPath(Path.withoutDirectory(projectFile));
|
||||
var name = Path.withoutDirectory(Path.withoutExtension(projectFile));
|
||||
name = name.substr(0, 1).toUpperCase() + name.substr(1);
|
||||
name = name.charAt(0).toUpperCase() + name.substr(1);
|
||||
|
||||
var tempDirectory = System.getTemporaryDirectory();
|
||||
var tempDirectory = FileSystem.fullPath(System.getTemporaryDirectory());
|
||||
var classFile = Path.combine(tempDirectory, name + ".hx");
|
||||
|
||||
System.copyFile(path, classFile);
|
||||
@@ -419,6 +431,7 @@ class HXProject extends Script
|
||||
name: name,
|
||||
target: HXProject._target,
|
||||
debug: HXProject._debug,
|
||||
projectFile: projectFile,
|
||||
targetFlags: HXProject._targetFlags,
|
||||
templatePaths: HXProject._templatePaths,
|
||||
userDefines: HXProject._userDefines,
|
||||
@@ -434,7 +447,7 @@ class HXProject extends Script
|
||||
try
|
||||
{
|
||||
#if (lime && !eval)
|
||||
var nekoOutput = FileSystem.fullPath(Path.combine(tempDirectory, name + ".n"));
|
||||
var nekoOutput = Path.combine(tempDirectory, name + ".n");
|
||||
System.runCommand("", "haxe", args.concat(["--main", "lime.tools.HXProject", "-neko", nekoOutput]));
|
||||
System.runCommand("", "neko", [nekoOutput, inputFile, outputFile]);
|
||||
#else
|
||||
@@ -443,6 +456,7 @@ class HXProject extends Script
|
||||
}
|
||||
catch (e:Dynamic)
|
||||
{
|
||||
Log.error(Std.string(e));
|
||||
FileSystem.deleteFile(inputFile);
|
||||
Sys.exit(1);
|
||||
}
|
||||
@@ -659,15 +673,183 @@ class HXProject extends Script
|
||||
@:privateAccess projectXML.parseXML(new Access(Xml.parse(xml).firstElement()), "");
|
||||
merge(projectXML);
|
||||
}
|
||||
|
||||
// #end
|
||||
private static function initialize():Void
|
||||
|
||||
private function initializeDefines():Void
|
||||
{
|
||||
switch (platformType)
|
||||
{
|
||||
case MOBILE:
|
||||
defines.set("platformType", "mobile");
|
||||
defines.set("mobile", "1");
|
||||
|
||||
case DESKTOP:
|
||||
defines.set("platformType", "desktop");
|
||||
defines.set("desktop", "1");
|
||||
|
||||
case WEB:
|
||||
defines.set("platformType", "web");
|
||||
defines.set("web", "1");
|
||||
|
||||
case CONSOLE:
|
||||
defines.set("platformType", "console");
|
||||
defines.set("console", "1");
|
||||
}
|
||||
|
||||
if (targetFlags.exists("neko"))
|
||||
{
|
||||
defines.set("targetType", "neko");
|
||||
defines.set("native", "1");
|
||||
defines.set("neko", "1");
|
||||
}
|
||||
else if (targetFlags.exists("hl"))
|
||||
{
|
||||
defines.set("targetType", "hl");
|
||||
defines.set("native", "1");
|
||||
defines.set("hl", "1");
|
||||
if (targetFlags.exists("hlc"))
|
||||
{
|
||||
defines.set("hlc", "1");
|
||||
}
|
||||
}
|
||||
else if (targetFlags.exists("java"))
|
||||
{
|
||||
defines.set("targetType", "java");
|
||||
defines.set("native", "1");
|
||||
defines.set("java", "1");
|
||||
}
|
||||
else if (targetFlags.exists("nodejs"))
|
||||
{
|
||||
defines.set("targetType", "nodejs");
|
||||
defines.set("native", "1");
|
||||
defines.set("nodejs", "1");
|
||||
}
|
||||
else if (targetFlags.exists("cs"))
|
||||
{
|
||||
defines.set("targetType", "cs");
|
||||
defines.set("native", "1");
|
||||
defines.set("cs", "1");
|
||||
}
|
||||
else if (target == Platform.FIREFOX)
|
||||
{
|
||||
defines.set("targetType", "js");
|
||||
defines.set("html5", "1");
|
||||
}
|
||||
else if (target == Platform.AIR)
|
||||
{
|
||||
defines.set("targetType", "swf");
|
||||
defines.set("flash", "1");
|
||||
if (targetFlags.exists("ios")) defines.set("ios", "1");
|
||||
if (targetFlags.exists("android")) defines.set("android", "1");
|
||||
}
|
||||
else if (target == Platform.WINDOWS && (targetFlags.exists("uwp") || targetFlags.exists("winjs")))
|
||||
{
|
||||
targetFlags.set("uwp", "");
|
||||
targetFlags.set("winjs", "");
|
||||
|
||||
defines.set("targetType", "js");
|
||||
defines.set("html5", "1");
|
||||
defines.set("uwp", "1");
|
||||
defines.set("winjs", "1");
|
||||
}
|
||||
else if (platformType == DESKTOP && target != System.hostPlatform)
|
||||
{
|
||||
defines.set("native", "1");
|
||||
|
||||
if (target == Platform.LINUX && targetFlags.exists("cpp"))
|
||||
{
|
||||
defines.set("targetType", "cpp");
|
||||
defines.set("cpp", "1");
|
||||
}
|
||||
else if (target == Platform.WINDOWS && targetFlags.exists("mingw"))
|
||||
{
|
||||
defines.set("targetType", "cpp");
|
||||
defines.set("cpp", "1");
|
||||
defines.set("mingw", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
defines.set("targetType", "neko");
|
||||
defines.set("neko", "1");
|
||||
}
|
||||
}
|
||||
else if (target == Platform.WEB_ASSEMBLY)
|
||||
{
|
||||
defines.set("webassembly", "1");
|
||||
defines.set("wasm", "1");
|
||||
defines.set("emscripten", "1");
|
||||
defines.set("targetType", "cpp");
|
||||
defines.set("native", "1");
|
||||
defines.set("cpp", "1");
|
||||
}
|
||||
else if (targetFlags.exists("cpp")
|
||||
|| ((platformType != PlatformType.WEB) && !targetFlags.exists("html5")))
|
||||
{
|
||||
defines.set("targetType", "cpp");
|
||||
defines.set("native", "1");
|
||||
defines.set("cpp", "1");
|
||||
}
|
||||
else if (target == Platform.FLASH)
|
||||
{
|
||||
defines.set("targetType", "swf");
|
||||
}
|
||||
|
||||
if (debug)
|
||||
{
|
||||
defines.set("buildType", "debug");
|
||||
defines.set("debug", "1");
|
||||
}
|
||||
else if (targetFlags.exists("final"))
|
||||
{
|
||||
defines.set("buildType", "final");
|
||||
defines.set("final", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
defines.set("buildType", "release");
|
||||
defines.set("release", "1");
|
||||
}
|
||||
|
||||
if (targetFlags.exists("static"))
|
||||
{
|
||||
defines.set("static_link", "1");
|
||||
}
|
||||
|
||||
if (defines.exists("SWF_PLAYER"))
|
||||
{
|
||||
environment.set("SWF_PLAYER", defines.get("SWF_PLAYER"));
|
||||
}
|
||||
|
||||
defines.set(Std.string(target).toLowerCase(), "1");
|
||||
defines.set("target", Std.string(target).toLowerCase());
|
||||
defines.set("platform", defines.get("target"));
|
||||
|
||||
switch (System.hostPlatform)
|
||||
{
|
||||
case WINDOWS:
|
||||
defines.set("host", "windows");
|
||||
case MAC:
|
||||
defines.set("host", "mac");
|
||||
case LINUX:
|
||||
defines.set("host", "linux");
|
||||
default:
|
||||
defines.set("host", "unknown");
|
||||
}
|
||||
|
||||
#if lime
|
||||
defines.set("lime-tools", "1");
|
||||
#end
|
||||
|
||||
defines.set("hxp", "1"); // TODO: Version?
|
||||
}
|
||||
|
||||
private static function initializeStatics():Void
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
if (_target == null)
|
||||
{
|
||||
_target = cast System.hostPlatform;
|
||||
_target = System.hostPlatform;
|
||||
}
|
||||
|
||||
if (_targetFlags == null)
|
||||
@@ -739,6 +921,11 @@ class HXProject extends Script
|
||||
launchStoryboard.merge(project.launchStoryboard);
|
||||
}
|
||||
|
||||
if (projectFilePath == null)
|
||||
{
|
||||
projectFilePath = project.projectFilePath;
|
||||
}
|
||||
|
||||
languages = ArrayTools.concatUnique(languages, project.languages, true);
|
||||
libraries = ArrayTools.concatUnique(libraries, project.libraries, true);
|
||||
|
||||
@@ -868,7 +1055,7 @@ class HXProject extends Script
|
||||
// Getters & Setters
|
||||
private function get_host():Platform
|
||||
{
|
||||
return cast System.hostPlatform;
|
||||
return System.hostPlatform;
|
||||
}
|
||||
|
||||
private function get_templateContext():Dynamic
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package lime.tools;
|
||||
|
||||
import hxp.Haxelib;
|
||||
import hxp.Log;
|
||||
import sys.FileSystem;
|
||||
import lime.tools.ConfigHelper;
|
||||
@@ -32,10 +33,25 @@ class HashlinkHelper
|
||||
{
|
||||
System.recursiveCopyTemplate(project.templatePaths, 'bin/hl/$bindir', applicationDirectory);
|
||||
System.renameFile(Path.combine(applicationDirectory, "hl" + (project.target == WINDOWS ? ".exe" : "")), executablePath);
|
||||
|
||||
if (project.targetFlags.exists("hlc"))
|
||||
{
|
||||
var limeDirectory = Haxelib.getPath(new Haxelib("lime"), true);
|
||||
var includeDirectory = sys.FileSystem.exists(Path.combine(limeDirectory, "project"))
|
||||
? Path.combine(limeDirectory, "project/lib/hashlink/src")
|
||||
: Path.combine(limeDirectory, "templates/bin/hl/include");
|
||||
|
||||
System.copyFile(Path.combine(includeDirectory, "hlc.h"), Path.combine(targetDirectory, "obj/hlc.h"), null, false);
|
||||
System.copyFile(Path.combine(includeDirectory, "hl.h"), Path.combine(targetDirectory, "obj/hl.h"), null, false);
|
||||
System.copyFile(Path.combine(includeDirectory, "hlc_main.c"), Path.combine(targetDirectory, "obj/hlc_main.c"), null, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.copyFile(Path.combine(hlPath, "hl" + (platform == WINDOWS ? ".exe" : "")), executablePath);
|
||||
if (!project.targetFlags.exists("hlc"))
|
||||
{
|
||||
System.copyFile(Path.combine(hlPath, "hl" + (platform == WINDOWS ? ".exe" : "")), executablePath);
|
||||
}
|
||||
if (platform == WINDOWS)
|
||||
{
|
||||
System.copyFile(Path.combine(hlPath, "libhl.dll"), Path.combine(applicationDirectory, "libhl.dll"));
|
||||
@@ -66,6 +82,14 @@ class HashlinkHelper
|
||||
{
|
||||
System.copyFile(file, Path.combine(applicationDirectory, Path.withoutDirectory(file)));
|
||||
}
|
||||
|
||||
if (project.targetFlags.exists("hlc"))
|
||||
{
|
||||
for (file in System.readDirectory(Path.combine(hlPath, "include")))
|
||||
{
|
||||
System.copyFile(file, Path.combine(targetDirectory, Path.combine("obj", Path.withoutDirectory(file))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure no hxcpp hash files or MSVC build artifacts remain
|
||||
@@ -74,11 +98,62 @@ class HashlinkHelper
|
||||
{
|
||||
switch Path.extension(file)
|
||||
{
|
||||
case "hash", "lib", "pdb", "ilk", "exp":
|
||||
case "hash", "pdb", "ilk", "exp":
|
||||
System.deleteFile(file);
|
||||
case "lib":
|
||||
if (platform != WINDOWS)
|
||||
{
|
||||
System.deleteFile(file);
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
System.copyFile(targetDirectory + "/obj/ApplicationMain.hl", Path.combine(applicationDirectory, "hlboot.dat"));
|
||||
if (project.targetFlags.exists("hlc"))
|
||||
{
|
||||
if (sys.FileSystem.exists(executablePath))
|
||||
{
|
||||
System.deleteFile(executablePath);
|
||||
}
|
||||
|
||||
var appMainCPath = Path.combine(targetDirectory, "obj/ApplicationMain.c");
|
||||
var appMainCText = System.readText(appMainCPath);
|
||||
var index = appMainCText.indexOf("#ifndef HL_MAKE");
|
||||
appMainCText = appMainCText.substr(0, index) + "
|
||||
// --------- START LIME HL/C INJECTED CODE --------- //
|
||||
// undefine things to avoid Haxe field name conflicts
|
||||
#undef BIG_ENDIAN
|
||||
#undef LITTLE_ENDIAN
|
||||
#undef TRUE
|
||||
#undef FALSE
|
||||
#undef BOOLEAN
|
||||
#undef ERROR
|
||||
#undef NO_ERROR
|
||||
#undef DELETE
|
||||
#undef OPTIONS
|
||||
#undef IN
|
||||
#undef OUT
|
||||
#undef ALTERNATE
|
||||
#undef OPTIONAL
|
||||
#undef DOUBLE_CLICK
|
||||
#undef DIFFERENCE
|
||||
#undef POINT
|
||||
#undef RECT
|
||||
#undef OVERFLOW
|
||||
#undef UNDERFLOW
|
||||
#undef DOMAIN
|
||||
#undef TRANSPARENT
|
||||
#undef CONST
|
||||
#undef CopyFile
|
||||
#undef COLOR_HIGHLIGHT
|
||||
#undef __valid
|
||||
#undef WAIT_FAILED
|
||||
// ---------- END LIME HL/C INJECTED CODE ---------- //
|
||||
" + appMainCText.substr(index);
|
||||
System.writeText(appMainCText, appMainCPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.copyFile(Path.combine(targetDirectory, "obj/ApplicationMain.hl"), Path.combine(applicationDirectory, "hlboot.dat"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,13 @@ class IOSHelper
|
||||
else
|
||||
{
|
||||
commands.push("build");
|
||||
if (project.targetFlags.exists("nosign"))
|
||||
{
|
||||
commands.push("CODE_SIGN_IDENTITY=\"\"");
|
||||
commands.push("CODE_SIGNING_REQUIRED=\"NO\"");
|
||||
commands.push("CODE_SIGN_ENTITLEMENTS=\"\"");
|
||||
commands.push("CODE_SIGNING_ALLOWED=\"NO\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalArguments != null)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package lime.tools;
|
||||
|
||||
#if (haxe_ver >= 4.0) enum #else @:enum #end abstract Platform(String)
|
||||
#if (haxe_ver >= 4.0) enum #else @:enum #end abstract Platform(String) from hxp.HostPlatform
|
||||
{
|
||||
var AIR = "air";
|
||||
var ANDROID = "android";
|
||||
@@ -24,4 +24,10 @@ package lime.tools;
|
||||
var EMSCRIPTEN = "emscripten";
|
||||
var TVOS = "tvos";
|
||||
var CUSTOM = null;
|
||||
|
||||
@:op(A == B) @:commutative
|
||||
private inline function equalsHostPlatform(hostPlatform:hxp.HostPlatform):Bool
|
||||
{
|
||||
return this == hostPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@ import haxe.io.Path;
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.app.Event;
|
||||
import lime.graphics.Image;
|
||||
import lime.system.BackgroundWorker;
|
||||
import lime.system.CFFI;
|
||||
import lime.system.ThreadPool;
|
||||
import lime.utils.ArrayBuffer;
|
||||
import lime.utils.Resource;
|
||||
#if hl
|
||||
@@ -98,97 +99,7 @@ class FileDialog
|
||||
if (type == null) type = FileDialogType.OPEN;
|
||||
|
||||
#if desktop
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OPEN:
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case OPEN_MULTIPLE:
|
||||
#if linux
|
||||
if (title == null) title = "Open Files";
|
||||
#end
|
||||
|
||||
var paths = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes:NativeArray<HLBytes> = cast NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
paths = [];
|
||||
for (i in 0...bytes.length)
|
||||
{
|
||||
paths[i] = @:privateAccess String.fromUTF8(bytes[i]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
paths = NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(paths);
|
||||
|
||||
case OPEN_DIRECTORY:
|
||||
#if linux
|
||||
if (title == null) title = "Open Directory";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case SAVE:
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
}
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
}
|
||||
});
|
||||
var worker = new ThreadPool();
|
||||
|
||||
worker.onComplete.add(function(result)
|
||||
{
|
||||
@@ -226,7 +137,71 @@ class FileDialog
|
||||
}
|
||||
});
|
||||
|
||||
worker.run();
|
||||
worker.run(function(_, __)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case OPEN:
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
path = CFFI.stringValue(NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath));
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case OPEN_MULTIPLE:
|
||||
#if linux
|
||||
if (title == null) title = "Open Files";
|
||||
#end
|
||||
|
||||
var paths = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes:NativeArray<HLBytes> = cast NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
if (bytes != null)
|
||||
{
|
||||
paths = [];
|
||||
for (i in 0...bytes.length)
|
||||
{
|
||||
paths[i] = CFFI.stringValue(bytes[i]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
paths = NativeCFFI.lime_file_dialog_open_files(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(paths);
|
||||
|
||||
case OPEN_DIRECTORY:
|
||||
#if linux
|
||||
if (title == null) title = "Open Directory";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
path = CFFI.stringValue(NativeCFFI.lime_file_dialog_open_directory(title, filter, defaultPath));
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
|
||||
case SAVE:
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
path = CFFI.stringValue(NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath));
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
#else
|
||||
@@ -248,27 +223,8 @@ class FileDialog
|
||||
**/
|
||||
public function open(filter:String = null, defaultPath:String = null, title:String = null):Bool
|
||||
{
|
||||
#if desktop
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
if (bytes != null) path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
#if (desktop && sys)
|
||||
var worker = new ThreadPool();
|
||||
|
||||
worker.onComplete.add(function(path:String)
|
||||
{
|
||||
@@ -286,7 +242,19 @@ class FileDialog
|
||||
onCancel.dispatch();
|
||||
});
|
||||
|
||||
worker.run();
|
||||
worker.run(function(_, __)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Open File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
path = CFFI.stringValue(NativeCFFI.lime_file_dialog_open_file(title, filter, defaultPath));
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
|
||||
return true;
|
||||
#else
|
||||
@@ -318,27 +286,8 @@ class FileDialog
|
||||
return false;
|
||||
}
|
||||
|
||||
#if desktop
|
||||
var worker = new BackgroundWorker();
|
||||
|
||||
worker.doWork.add(function(_)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
#if hl
|
||||
var bytes = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
path = @:privateAccess String.fromUTF8(cast bytes);
|
||||
#else
|
||||
path = NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath);
|
||||
#end
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
#if (desktop && sys)
|
||||
var worker = new ThreadPool();
|
||||
|
||||
worker.onComplete.add(function(path:String)
|
||||
{
|
||||
@@ -356,7 +305,19 @@ class FileDialog
|
||||
onCancel.dispatch();
|
||||
});
|
||||
|
||||
worker.run();
|
||||
worker.run(function(_, __)
|
||||
{
|
||||
#if linux
|
||||
if (title == null) title = "Save File";
|
||||
#end
|
||||
|
||||
var path = null;
|
||||
#if (!macro && lime_cffi)
|
||||
path = CFFI.stringValue(NativeCFFI.lime_file_dialog_save_file(title, filter, defaultPath));
|
||||
#end
|
||||
|
||||
worker.sendComplete(path);
|
||||
});
|
||||
|
||||
return true;
|
||||
#elseif (js && html5)
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.ui;
|
||||
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.app.Event;
|
||||
import lime.system.CFFI;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@@ -64,11 +65,7 @@ class Gamepad
|
||||
@:noCompletion private inline function get_guid():String
|
||||
{
|
||||
#if (lime_cffi && !macro)
|
||||
#if hl
|
||||
return @:privateAccess String.fromUTF8(NativeCFFI.lime_gamepad_get_device_guid(this.id));
|
||||
#else
|
||||
return NativeCFFI.lime_gamepad_get_device_guid(this.id);
|
||||
#end
|
||||
return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_guid(this.id));
|
||||
#elseif (js && html5)
|
||||
var devices = Joystick.__getDeviceData();
|
||||
return devices[this.id].id;
|
||||
@@ -80,11 +77,7 @@ class Gamepad
|
||||
@:noCompletion private inline function get_name():String
|
||||
{
|
||||
#if (lime_cffi && !macro)
|
||||
#if hl
|
||||
return @:privateAccess String.fromUTF8(NativeCFFI.lime_gamepad_get_device_name(this.id));
|
||||
#else
|
||||
return NativeCFFI.lime_gamepad_get_device_name(this.id);
|
||||
#end
|
||||
return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_name(this.id));
|
||||
#elseif (js && html5)
|
||||
var devices = Joystick.__getDeviceData();
|
||||
return devices[this.id].id;
|
||||
|
||||
@@ -2,6 +2,7 @@ package lime.ui;
|
||||
|
||||
import lime._internal.backend.native.NativeCFFI;
|
||||
import lime.app.Event;
|
||||
import lime.system.CFFI;
|
||||
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@@ -20,13 +21,11 @@ class Joystick
|
||||
public var numAxes(get, never):Int;
|
||||
public var numButtons(get, never):Int;
|
||||
public var numHats(get, never):Int;
|
||||
public var numTrackballs(get, never):Int;
|
||||
public var onAxisMove = new Event<Int->Float->Void>();
|
||||
public var onButtonDown = new Event<Int->Void>();
|
||||
public var onButtonUp = new Event<Int->Void>();
|
||||
public var onDisconnect = new Event<Void->Void>();
|
||||
public var onHatMove = new Event<Int->JoystickHatPosition->Void>();
|
||||
public var onTrackballMove = new Event<Int->Float->Float->Void>();
|
||||
|
||||
public function new(id:Int)
|
||||
{
|
||||
@@ -75,11 +74,7 @@ class Joystick
|
||||
@:noCompletion private inline function get_guid():String
|
||||
{
|
||||
#if (lime_cffi && !macro)
|
||||
#if hl
|
||||
return @:privateAccess String.fromUTF8(NativeCFFI.lime_joystick_get_device_guid(this.id));
|
||||
#else
|
||||
return NativeCFFI.lime_joystick_get_device_guid(this.id);
|
||||
#end
|
||||
return CFFI.stringValue(NativeCFFI.lime_joystick_get_device_guid(this.id));
|
||||
#elseif (js && html5)
|
||||
var devices = __getDeviceData();
|
||||
return devices[this.id].id;
|
||||
@@ -91,11 +86,7 @@ class Joystick
|
||||
@:noCompletion private inline function get_name():String
|
||||
{
|
||||
#if (lime_cffi && !macro)
|
||||
#if hl
|
||||
return @:privateAccess String.fromUTF8(NativeCFFI.lime_joystick_get_device_name(this.id));
|
||||
#else
|
||||
return NativeCFFI.lime_joystick_get_device_name(this.id);
|
||||
#end
|
||||
return CFFI.stringValue(NativeCFFI.lime_joystick_get_device_name(this.id));
|
||||
#elseif (js && html5)
|
||||
var devices = __getDeviceData();
|
||||
return devices[this.id].id;
|
||||
@@ -136,13 +127,4 @@ class Joystick
|
||||
return 0;
|
||||
#end
|
||||
}
|
||||
|
||||
@:noCompletion private inline function get_numTrackballs():Int
|
||||
{
|
||||
#if (lime_cffi && !macro)
|
||||
return NativeCFFI.lime_joystick_get_num_trackballs(this.id);
|
||||
#else
|
||||
return 0;
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +518,11 @@ class Assets
|
||||
}
|
||||
|
||||
public static function unloadLibrary(name:String):Void
|
||||
{
|
||||
removeLibrary(name, true);
|
||||
}
|
||||
|
||||
public static function removeLibrary(name:String, unload:Bool = true):Void
|
||||
{
|
||||
#if (tools && !display)
|
||||
if (name == null || name == "")
|
||||
@@ -531,7 +536,10 @@ class Assets
|
||||
{
|
||||
cache.clear(name + ":");
|
||||
library.onChange.remove(library_onChange);
|
||||
library.unload();
|
||||
if (unload)
|
||||
{
|
||||
library.unload();
|
||||
}
|
||||
}
|
||||
|
||||
libraries.remove(name);
|
||||
|
||||
@@ -2,14 +2,29 @@ package lime.utils;
|
||||
|
||||
import haxe.ds.ObjectMap;
|
||||
|
||||
|
||||
/**
|
||||
A generic object pool for reusing objects.
|
||||
**/
|
||||
#if !lime_debug
|
||||
@:fileXml('tags="haxe,release"')
|
||||
@:noDebug
|
||||
#end
|
||||
#if !js @:generic #end class ObjectPool<T>
|
||||
{
|
||||
/**
|
||||
The number of active objects in the pool.
|
||||
**/
|
||||
public var activeObjects(default, null):Int;
|
||||
|
||||
/**
|
||||
The number of inactive objects in the pool.
|
||||
**/
|
||||
public var inactiveObjects(default, null):Int;
|
||||
|
||||
/**
|
||||
The total size of the object pool (both active and inactive objects).
|
||||
**/
|
||||
public var size(get, set):Null<Int>;
|
||||
|
||||
@:noCompletion private var __inactiveObject0:T;
|
||||
@@ -18,6 +33,13 @@ import haxe.ds.ObjectMap;
|
||||
@:noCompletion private var __pool:Map<T, Bool>;
|
||||
@:noCompletion private var __size:Null<Int>;
|
||||
|
||||
/**
|
||||
Creates a new ObjectPool instance.
|
||||
|
||||
@param create A function that creates a new instance of type T.
|
||||
@param clean A function that cleans up an instance of type T before it is reused.
|
||||
@param size The maximum size of the object pool.
|
||||
**/
|
||||
public function new(create:Void->T = null, clean:T->Void = null, size:Null<Int> = null)
|
||||
{
|
||||
__pool = cast new ObjectMap();
|
||||
@@ -42,10 +64,14 @@ import haxe.ds.ObjectMap;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an object to the object pool.
|
||||
|
||||
@param object The object to add to the pool.
|
||||
**/
|
||||
public function add(object:T):Void
|
||||
{
|
||||
if (!__pool.exists(object))
|
||||
if (object != null && !__pool.exists(object))
|
||||
{
|
||||
__pool.set(object, false);
|
||||
clean(object);
|
||||
@@ -53,8 +79,18 @@ import haxe.ds.ObjectMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Dynamic function.
|
||||
|
||||
Cleans up an object before returning it to the pool.
|
||||
|
||||
@param object The object to clean up.
|
||||
**/
|
||||
public dynamic function clean(object:T):Void {}
|
||||
|
||||
/**
|
||||
Clears the object pool, removing all objects.
|
||||
**/
|
||||
public function clear():Void
|
||||
{
|
||||
__pool = cast new ObjectMap();
|
||||
@@ -67,11 +103,21 @@ import haxe.ds.ObjectMap;
|
||||
__inactiveObjectList.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
Dynamic function.
|
||||
|
||||
Creates a new Object.
|
||||
**/
|
||||
public dynamic function create():T
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new object and adds it to the pool, or returns an existing inactive object from the pool.
|
||||
|
||||
@return The object retrieved from the pool, or null if the pool is full and no new objects can be created.
|
||||
**/
|
||||
public function get():T
|
||||
{
|
||||
var object = null;
|
||||
@@ -94,10 +140,15 @@ import haxe.ds.ObjectMap;
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
Releases an active object back into the pool.
|
||||
|
||||
@param object The object to release.
|
||||
**/
|
||||
public function release(object:T):Void
|
||||
{
|
||||
#if debug
|
||||
if (!__pool.exists(object))
|
||||
if (object == null || !__pool.exists(object))
|
||||
{
|
||||
Log.error("Object is not a member of the pool");
|
||||
}
|
||||
@@ -120,9 +171,14 @@ import haxe.ds.ObjectMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes an object from the pool.
|
||||
|
||||
@param object The object to remove from the pool.
|
||||
**/
|
||||
public function remove(object:T):Void
|
||||
{
|
||||
if (__pool.exists(object))
|
||||
if (object != null && __pool.exists(object))
|
||||
{
|
||||
__pool.remove(object);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user