diff --git a/project/Build.xml b/project/Build.xml index d6f0d7c857..2c518ca0e2 100755 --- a/project/Build.xml +++ b/project/Build.xml @@ -8,8 +8,8 @@ - - + + diff --git a/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx b/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx new file mode 100644 index 0000000000..91fc89814e --- /dev/null +++ b/src/lime/_internal/backend/emscripten/EmscriptenFetch.hx @@ -0,0 +1,50 @@ +package lime._internal.backend.emscripten; + +#if emscripten +import cpp.Star; +import cpp.Pointer; +import cpp.ConstCharStar; + +@:include("emscripten/fetch.h") +@:native +extern class EmscriptenFetch { + + @:native("emscripten_fetch_attr_init") + public static function emscripten_fetch_attr_init(attr:Pointer):Void; + + @:native("emscripten_fetch") + public static function emscripten_fetch(attr:Pointer, url:ConstCharStar):Pointer; + + @:native("emscripten_fetch_close") + public static function emscripten_fetch_close(fetch:Pointer):Void; +} + +@:include("emscripten/fetch.h") +@:native("emscripten_fetch_attr_t") +@:structAccess +@:unreflective +extern class EmscriptenFetchAttrType { + public var requestMethod:ConstCharStar; + public var onsuccess:Star->Void; + public var onprogress:Star->Void; + public var onerror:Star->Void; + public var attributes:Int; + public var userData:Star; +} + +@:include("emscripten/fetch.h") +@:native("emscripten_fetch_t") +@:structAccess +extern class EmscriptenFetchType { + public var id:Int; + public var numBytes:Int; + public var dataOffset:Int; + public var totalBytes:Int; + public var url:ConstCharStar; + public var readyState:Int; + public var status:Int; + public var statusText:ConstCharStar; + public var data:Pointer; + public var userData:Star; +} +#end \ No newline at end of file diff --git a/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx b/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx new file mode 100644 index 0000000000..435afeff94 --- /dev/null +++ b/src/lime/_internal/backend/emscripten/EmscriptenHTTPRequest.hx @@ -0,0 +1,253 @@ +package lime._internal.backend.emscripten; + +#if emscripten +import haxe.io.Bytes; +import cpp.Pointer; +import cpp.Star; +import lime.app.Future; +import lime.app.Promise; +import lime.graphics.Image; +import lime.net.HTTPRequest._IHTTPRequest; +import lime._internal.backend.emscripten.EmscriptenFetch.EmscriptenFetchAttrType; +import lime._internal.backend.emscripten.EmscriptenFetch.EmscriptenFetchType; +import lime.net.HTTPRequest._HTTPRequestErrorResponse; + +@:access(lime.graphics.ImageBuffer) +@:access(lime.graphics.Image) +class EmscriptenHTTPRequest +{ + private static var fetchId:Int = 0; + private static var activeFetches = new Map(); + + private var binary:Bool; + private var parent:_IHTTPRequest; + private var currentFetchId:Int = -1; + + public function new() + { + } + + public function cancel():Void + { + if (currentFetchId >= 0 && activeFetches.exists(currentFetchId)) + { + var context = activeFetches.get(currentFetchId); + if (context != null && context.fetchPtr != null) + { + EmscriptenFetch.emscripten_fetch_close(context.fetchPtr); + } + activeFetches.remove(currentFetchId); + currentFetchId = -1; + } + } + + public function init(parent:_IHTTPRequest):Void + { + this.parent = parent; + } + + public function loadData(uri:String):Future + { + var promise = new Promise(); + binary = true; + __load(uri, promise, LoadType.BINARY); + return promise.future; + } + + public function loadText(uri:String):Future + { + var promise = new Promise(); + binary = false; + __load(uri, promise, LoadType.TEXT); + return promise.future; + } + + private function __load(uri:String, promise:Dynamic, loadType:LoadType):Void + { + var id = fetchId++; + currentFetchId = id; + + var context:FetchContext = { + id: id, + promise: promise, + parent: parent, + loadType: loadType, + binary: binary, + fetchPtr: null + }; + + activeFetches.set(id, context); + + var attr:EmscriptenFetchAttrType = untyped __cpp__("{}"); + var attrPtr:Pointer = Pointer.addressOf(attr); + EmscriptenFetch.emscripten_fetch_attr_init(attrPtr); + + // Set request method + var method = parent.method != null ? Std.string(parent.method) : "GET"; + untyped __cpp__('strcpy({0}, {1})', attr.requestMethod, method); + + // Set callbacks + attr.onsuccess = untyped __cpp__("onFetchSuccess"); + attr.onprogress = untyped __cpp__("onFetchProgress"); + attr.onerror = untyped __cpp__("onFetchError"); + + // Store fetch ID in userData + var idPointer:Star = untyped __cpp__('(int*)malloc(sizeof(int))'); + untyped __cpp__('*{0} = {1}', idPointer, id); + attr.userData = cast idPointer; + + // Set EMSCRIPTEN_FETCH_LOAD_TO_MEMORY + attr.attributes = 1; + + // Start fetch + var fetchPtr = EmscriptenFetch.emscripten_fetch(attrPtr, uri); + context.fetchPtr = fetchPtr; + } + + @:keep + private static function onFetchSuccess(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + cpp.Native.free(idPointer); + fetch.userData = null; + + if (!activeFetches.exists(id)) + { + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + return; + } + + var context = activeFetches.get(id); + activeFetches.remove(id); + if(context == null) + trace("EmscriptenHTTPRequest.onFetchSuccess: context is null"); + + // Process response + if (context.parent.enableResponseHeaders) + { + context.parent.responseHeaders = []; + // Note: Emscripten Fetch API has limited header access + // You may need to parse headers from fetch.statusText if available + } + + context.parent.responseStatus = fetch.status; + + // Create bytes from fetch data + var bytes:Bytes = null; + if (fetch.numBytes > 0 && fetch.data != null) + { + bytes = Bytes.ofData(cast fetch.data.toUnmanagedArray(fetch.numBytes).copy()); + } + else + { + bytes = Bytes.alloc(0); + } + + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + + // Complete promise based on load type + if (fetch.status >= 200 && fetch.status < 400) + { + switch (context.loadType) + { + case LoadType.BINARY: + cast(context.promise, Promise).complete(bytes); + + case LoadType.TEXT: + var text = bytes.getString(0, bytes.length, UTF8); + cast(context.promise, Promise).complete(text); + + case LoadType.IMAGE: + // For images, we need to decode the bytes + var img = new Image(); + img.__fromBytes(bytes, function(image) + { + cast(context.promise, Promise).complete(image); + }); + } + } + else + { + var errorResponse:_HTTPRequestErrorResponse; + if(context.loadType == LoadType.BINARY) { + errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes); + cast(context.promise, Promise).error(errorResponse); + }else { + errorResponse = new _HTTPRequestErrorResponse(fetch.status, bytes.getString(0, bytes.length, UTF8)); + cast(context.promise, Promise).error(errorResponse); + } + } + } + + @:keep + private static function onFetchProgress(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + if (activeFetches.exists(id)) + { + var context = activeFetches.get(id); + if (fetch.totalBytes > 0) + { + if(context.loadType == LoadType.BINARY) + cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, fetch.totalBytes); + else if(context.loadType == LoadType.TEXT) + cast(context.promise, Promise).progress(fetch.dataOffset + fetch.numBytes, fetch.totalBytes); + } + } + } + + @:keep + private static function onFetchError(fetchPtr:Star):Void + { + var fetch:EmscriptenFetchType = Pointer.fromStar(fetchPtr).value; + var idPointer:Star = cast fetch.userData; + var id:Int = untyped __cpp__('*(int*){0}', idPointer); + + cpp.Native.free(idPointer); + + if (!activeFetches.exists(id)) + { + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + return; + } + + var context = activeFetches.get(id); + activeFetches.remove(id); + + context.parent.responseStatus = fetch.status; + + EmscriptenFetch.emscripten_fetch_close(Pointer.fromStar(fetchPtr)); + + var errorResponse = new _HTTPRequestErrorResponse(fetch.status, null); + if(context.loadType == LoadType.BINARY) + cast(context.promise, Promise).error(errorResponse); + else + cast(context.promise, Promise).error(errorResponse); + } +} + +@:dox(hide) +enum LoadType +{ + BINARY; + TEXT; + IMAGE; +} + +@:dox(hide) +typedef FetchContext = +{ + var id:Int; + var promise:Dynamic; + var parent:_IHTTPRequest; + var loadType:LoadType; + var binary:Bool; + var fetchPtr:Pointer; +} +#end \ No newline at end of file diff --git a/src/lime/net/HTTPRequest.hx b/src/lime/net/HTTPRequest.hx index 6627103db5..70c0ac8fe8 100644 --- a/src/lime/net/HTTPRequest.hx +++ b/src/lime/net/HTTPRequest.hx @@ -193,6 +193,8 @@ public function load(uri:String = null):Future private typedef HTTPRequestBackend = lime._internal.backend.flash.FlashHTTPRequest; #elseif (js && html5) private typedef HTTPRequestBackend = lime._internal.backend.html5.HTML5HTTPRequest; +#elseif emscripten +private typedef HTTPRequestBackend = lime._internal.backend.emscripten.EmscriptenHTTPRequest; #else private typedef HTTPRequestBackend = lime._internal.backend.native.NativeHTTPRequest; #end diff --git a/templates/haxe/ManifestResources.hx b/templates/haxe/ManifestResources.hx index 0aaab0e4be..32c5dfe329 100644 --- a/templates/haxe/ManifestResources.hx +++ b/templates/haxe/ManifestResources.hx @@ -55,7 +55,7 @@ import sys.FileSystem; if (rootPath == null) { - #if (ios || tvos || webassembly) + #if (ios || tvos) rootPath = "assets/"; #elseif android rootPath = ""; diff --git a/templates/webassembly/template/index.html b/templates/webassembly/template/index.html index def2fa388e..33ab3e070c 100644 --- a/templates/webassembly/template/index.html +++ b/templates/webassembly/template/index.html @@ -55,11 +55,15 @@ Module.setStatus = function (msg) { console.log (msg); }; ::if (WIN_WIDTH == 0)::::if (WIN_HEIGHT == 0):: var container = document.getElementById ("content"); + Module.canvas.style.width = container.clientWidth + "px"; + Module.canvas.style.height = container.clientHeight + "px"; Module.canvas.width = container.clientWidth; Module.canvas.height = container.clientHeight; window.addEventListener ("resize", function (e) { + Module.canvas.style.width = container.clientWidth + "px"; + Module.canvas.style.height = container.clientHeight + "px"; Module.canvas.width = container.clientWidth; Module.canvas.height = container.clientHeight; }, true);::end::::end:: diff --git a/tools/platforms/WebAssemblyPlatform.hx b/tools/platforms/WebAssemblyPlatform.hx index 53b6cc34f1..7cac7f9b86 100644 --- a/tools/platforms/WebAssemblyPlatform.hx +++ b/tools/platforms/WebAssemblyPlatform.hx @@ -160,9 +160,7 @@ class WebAssemblyPlatform extends PlatformTarget } args = args.concat([ - prefix + "ApplicationMain" + (project.debug ? "-debug" : "") + ".a", - "-o", - "ApplicationMain.o" + prefix + "ApplicationMain" + (project.debug ? "-debug" : "") + ".a" ]); if (!project.targetFlags.exists("asmjs")) @@ -190,27 +188,21 @@ class WebAssemblyPlatform extends PlatformTarget } } - if (project.targetFlags.exists("final") || project.defines.exists("disable-exception-catching") || project.targetFlags.exists("disable-exception-catching")) - { - args.push("-s"); - args.push("DISABLE_EXCEPTION_CATCHING=1"); - } - else - { - args.push("-gsource-map"); - args.push("-s"); - args.push("DISABLE_EXCEPTION_CATCHING=0"); - args.push("-s"); - args.push("NO_DISABLE_EXCEPTION_CATCHING=1"); - args.push("-s"); - args.push("ASSERTIONS=1"); - // args.push("-s"); - // args.push("ASSERTIONS=2"); - // args.push("-s"); - // args.push("STACK_OVERFLOW_CHECK=2"); - // args.push("-s"); - // args.push("DEMANGLE_SUPPORT=1"); - } + // Fix Rendering + args.push("-s"); + args.push("MIN_WEBGL_VERSION=2"); + args.push("-s"); + args.push("MAX_WEBGL_VERSION=2"); + + // Fix GC + args.push("--Wno-limited-postlink-optimizations"); + // https://github.com/HaxeFoundation/hxcpp/blob/767fe94d19a041147c4f65dea02c89cb206a0758/toolchain/emscripten-toolchain.xml#L29-L33 + args.push("-s"); + args.push("BINARYEN_EXTRA_PASSES='--spill-pointers'"); + + // lime.ndll requires -fwasm-exceptions + args.push("-s"); + args.push("-fwasm-exceptions"); // set initial size // args.push("-s"); @@ -219,30 +211,32 @@ class WebAssemblyPlatform extends PlatformTarget args.push("-s"); args.push("STACK_SIZE=1MB"); + args.push("-s"); + args.push("FETCH=1"); + // args.push("-s"); // args.push("SAFE_HEAP=1"); - // if (project.targetFlags.exists("final")) - // { - // args.push("-O3"); - // } - // else if (!project.debug) - // { - // // args.push ("-s"); - // // args.push ("OUTLINING_LIMIT=70000"); - // args.push("-O2"); - // } - // else - // { - // args.push("-O1"); - // } - - // https://github.com/HaxeFoundation/hxcpp/issues/987 - args.push("-O0"); + if (project.targetFlags.exists("final")) + { + args.push("-O3"); + } + else if (!project.debug) + { + args.push("-O2"); + } + else + { + args.push("-O1"); + } args.push("-s"); args.push("ALLOW_MEMORY_GROWTH=1"); + if(project.targetFlags.exists("websocket")) { + args.push("-lwebsocket.js"); + } + if (project.targetFlags.exists("minify")) { // 02 enables minification @@ -258,12 +252,6 @@ class WebAssemblyPlatform extends PlatformTarget // args.push ("--jcache"); // args.push ("-g"); - if (FileSystem.exists(targetDirectory + "/obj/assets")) - { - args.push("--preload-file"); - args.push("assets"); - } - if (Log.verbose) { args.push("-v"); @@ -313,13 +301,6 @@ class WebAssemblyPlatform extends PlatformTarget if (project.targetFlags.exists("compress")) { - if (FileSystem.exists(targetDirectory + "/bin/" + project.app.file + ".data")) - { - // var byteArray = ByteArray.readFile (targetDirectory + "/bin/" + project.app.file + ".data"); - // byteArray.compress (CompressionAlgorithm.GZIP); - // File.saveBytes (targetDirectory + "/bin/" + project.app.file + ".data.compress", byteArray); - } - // var byteArray = ByteArray.readFile (targetDirectory + "/bin/" + project.app.file + ".js"); // byteArray.compress (CompressionAlgorithm.GZIP); // File.saveBytes (targetDirectory + "/bin/" + project.app.file + ".js.compress", byteArray); @@ -511,6 +492,11 @@ class WebAssemblyPlatform extends PlatformTarget System.mkdir(Path.directory(path)); AssetHelper.copyAsset(asset, path, context); } + else + { + System.mkdir(Path.directory(path)); + AssetHelper.copyAsset(asset, path, context); + } } }