Merge branch '8.2.0-Dev' into 9.0.0-dev

This commit is contained in:
Joseph Cloutier
2024-05-27 16:08:53 -04:00
160 changed files with 32036 additions and 3680 deletions

View File

@@ -1,6 +1,6 @@
package haxe;
#if !lime_cffi
#if (!lime_cffi || macro)
// Original haxe.Timer class
/*

View File

@@ -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

View 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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 {}

View File

@@ -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
{

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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. "

View File

@@ -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))
{

View File

@@ -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

View File

@@ -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();

View File

@@ -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);
}

View 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

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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"));
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -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);