diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index 8090eb4feb2f3..942e0123bb6bb 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -145,6 +145,7 @@ const EXECUTABLE_NAME = '$GODOT_BASENAME'; const MAIN_PACK = '$GODOT_BASENAME.pck'; + const GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS]; const INDETERMINATE_STATUS_STEP_MS = 100; const FULL_WINDOW = $GODOT_FULL_WINDOW; @@ -267,6 +268,7 @@ } else { setStatusMode('indeterminate'); engine.setCanvas(canvas); + engine.setGDNativeLibraries(GDNATIVE_LIBS); engine.startGame(EXECUTABLE_NAME, MAIN_PACK).then(() => { setStatusMode('hidden'); initializing = false; diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index bc05e5776a907..2f7837f817337 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -333,11 +333,11 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { platform_android.library_extension = "*.so"; platforms["Android"] = platform_android; - // TODO: Javascript platform is not supported yet - // NativePlatformConfig platform_html5; - // platform_html5.name = "HTML5"; - // platform_html5.library_extension = "*.wasm"; - // platforms["Javascript"] = platform_html5; + NativePlatformConfig platform_html5; + platform_html5.name = "HTML5"; + platform_html5.entries.push_back("wasm32"); + platform_html5.library_extension = "*.wasm"; + platforms["HTML5"] = platform_html5; NativePlatformConfig platform_ios; platform_ios.name = "iOS"; diff --git a/modules/webrtc/library_godot_webrtc.js b/modules/webrtc/library_godot_webrtc.js index f725a10a6f5b6..9f029407d2cd8 100644 --- a/modules/webrtc/library_godot_webrtc.js +++ b/modules/webrtc/library_godot_webrtc.js @@ -90,6 +90,7 @@ const GodotRTCDataChannel = { }, }, + godot_js_rtc_datachannel_ready_state_get__sig: 'ii', godot_js_rtc_datachannel_ready_state_get: function (p_id) { const ref = IDHandler.get(p_id); if (!ref) { @@ -109,6 +110,7 @@ const GodotRTCDataChannel = { } }, + godot_js_rtc_datachannel_send__sig: 'iiiii', godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) { const ref = IDHandler.get(p_id); if (!ref) { @@ -129,14 +131,17 @@ const GodotRTCDataChannel = { return 0; }, + godot_js_rtc_datachannel_is_ordered__sig: 'ii', godot_js_rtc_datachannel_is_ordered: function (p_id) { return IDHandler.get_prop(p_id, 'ordered', true); }, + godot_js_rtc_datachannel_id_get__sig: 'ii', godot_js_rtc_datachannel_id_get: function (p_id) { return IDHandler.get_prop(p_id, 'id', 65535); }, + godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii', godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) { const ref = IDHandler.get(p_id); if (!ref) { @@ -151,14 +156,17 @@ const GodotRTCDataChannel = { return 65535; }, + godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii', godot_js_rtc_datachannel_max_retransmits_get: function (p_id) { return IDHandler.get_prop(p_id, 'maxRetransmits', 65535); }, - godot_js_rtc_datachannel_is_negotiated: function (p_id, p_def) { + godot_js_rtc_datachannel_is_negotiated__sig: 'ii', + godot_js_rtc_datachannel_is_negotiated: function (p_id) { return IDHandler.get_prop(p_id, 'negotiated', 65535); }, + godot_js_rtc_datachannel_label_get__sig: 'ii', godot_js_rtc_datachannel_label_get: function (p_id) { const ref = IDHandler.get(p_id); if (!ref || !ref.label) { @@ -167,6 +175,7 @@ const GodotRTCDataChannel = { return GodotRuntime.allocString(ref.label); }, + godot_js_rtc_datachannel_protocol_get__sig: 'ii', godot_js_rtc_datachannel_protocol_get: function (p_id) { const ref = IDHandler.get(p_id); if (!ref || !ref.protocol) { @@ -175,11 +184,13 @@ const GodotRTCDataChannel = { return GodotRuntime.allocString(ref.protocol); }, + godot_js_rtc_datachannel_destroy__sig: 'vi', godot_js_rtc_datachannel_destroy: function (p_id) { GodotRTCDataChannel.close(p_id); IDHandler.remove(p_id); }, + godot_js_rtc_datachannel_connect__sig: 'viiiiii', godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) { const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref); const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref); @@ -188,6 +199,7 @@ const GodotRTCDataChannel = { GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose); }, + godot_js_rtc_datachannel_close__sig: 'vi', godot_js_rtc_datachannel_close: function (p_id) { const ref = IDHandler.get(p_id); if (!ref) { @@ -280,6 +292,7 @@ const GodotRTCPeerConnection = { }, }, + godot_js_rtc_pc_create__sig: 'iiiiii', godot_js_rtc_pc_create: function (p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) { const onstatechange = GodotRuntime.get_func(p_on_state_change).bind(null, p_ref); const oncandidate = GodotRuntime.get_func(p_on_candidate).bind(null, p_ref); @@ -302,6 +315,7 @@ const GodotRTCPeerConnection = { return id; }, + godot_js_rtc_pc_close__sig: 'vi', godot_js_rtc_pc_close: function (p_id) { const ref = IDHandler.get(p_id); if (!ref) { @@ -310,6 +324,7 @@ const GodotRTCPeerConnection = { ref.close(); }, + godot_js_rtc_pc_destroy__sig: 'vi', godot_js_rtc_pc_destroy: function (p_id) { const ref = IDHandler.get(p_id); if (!ref) { @@ -321,6 +336,7 @@ const GodotRTCPeerConnection = { IDHandler.remove(p_id); }, + godot_js_rtc_pc_offer_create__sig: 'viiii', godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) { const ref = IDHandler.get(p_id); if (!ref) { @@ -335,6 +351,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_local_description_set__sig: 'viiiii', godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) { const ref = IDHandler.get(p_id); if (!ref) { @@ -351,6 +368,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_remote_description_set__sig: 'viiiiii', godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) { const ref = IDHandler.get(p_id); if (!ref) { @@ -375,6 +393,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_ice_candidate_add__sig: 'viiii', godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) { const ref = IDHandler.get(p_id); if (!ref) { @@ -390,6 +409,7 @@ const GodotRTCPeerConnection = { }, godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'], + godot_js_rtc_pc_datachannel_create__sig: 'iiii', godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) { try { const ref = IDHandler.get(p_id); diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js index 6ada4e7335727..cf2c00a6a62e4 100644 --- a/modules/websocket/library_godot_websocket.js +++ b/modules/websocket/library_godot_websocket.js @@ -135,6 +135,7 @@ const GodotWebSocket = { }, }, + godot_js_websocket_create__sig: 'iiiiiiii', godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) { const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref); const on_message = GodotRuntime.get_func(p_on_message).bind(null, p_ref); @@ -156,6 +157,7 @@ const GodotWebSocket = { return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close); }, + godot_js_websocket_send__sig: 'iiiii', godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) { const bytes_array = new Uint8Array(p_buf_len); let i = 0; @@ -169,12 +171,14 @@ const GodotWebSocket = { return GodotWebSocket.send(p_id, out); }, + godot_js_websocket_close__sig: 'viii', godot_js_websocket_close: function (p_id, p_code, p_reason) { const code = p_code; const reason = GodotRuntime.parseString(p_reason); GodotWebSocket.close(p_id, code, reason); }, + godot_js_websocket_destroy__sig: 'vi', godot_js_websocket_destroy: function (p_id) { GodotWebSocket.destroy(p_id); }, diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index c280334c378d4..9c6894cfef059 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -11,13 +11,8 @@ javascript_files = [ "api/javascript_tools_editor_plugin.cpp", ] -build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] -if env["threads_enabled"]: - build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") - -build = env.add_program(build_targets, javascript_files) - -env.AddJSLibraries( +sys_env = env.Clone() +sys_env.AddJSLibraries( [ "js/libs/library_godot_audio.js", "js/libs/library_godot_display.js", @@ -28,12 +23,48 @@ env.AddJSLibraries( ) if env["tools"]: - env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) + sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) if env["javascript_eval"]: - env.AddJSLibraries(["js/libs/library_godot_eval.js"]) -for lib in env["JS_LIBS"]: - env.Append(LINKFLAGS=["--js-library", lib]) -env.Depends(build, env["JS_LIBS"]) + sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"]) +for lib in sys_env["JS_LIBS"]: + sys_env.Append(LINKFLAGS=["--js-library", lib]) + +build = [] +if env["gdnative_enabled"]: + build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] + # Reset libraries. The main runtime will only link emscripten libraries, not godot ones. + sys_env["LIBS"] = [] + # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. + sys_env.Append(LIBS=["idbfs.js"]) + # JS prepended to the module code loading the side library. + sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")]) + # Configure it as a main module (dynamic linking support). + sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"]) + sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) + sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"]) + sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"]) + # Force exporting the standard library (printf, malloc, etc.) + sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi" + # The main emscripten runtime, with exported standard libraries. + sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"]) + sys_env.Depends(sys, "js/dynlink.pre.js") + + # The side library, containing all Godot code. + wasm_env = env.Clone() + wasm_env.Append(CPPDEFINES=["WASM_GDNATIVE"]) # So that OS knows it can run GDNative libraries. + wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"]) + wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"]) + wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", javascript_files) + build = [sys[0], sys[1], wasm[0]] +else: + build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] + if env["threads_enabled"]: + build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") + # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. + sys_env.Append(LIBS=["idbfs.js"]) + build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"]) + +sys_env.Depends(build[0], sys_env["JS_LIBS"]) engine = [ "js/engine/preloader.js", @@ -60,8 +91,11 @@ out_files = [ ] html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] -if env["threads_enabled"]: - in_files.append(build[2]) +if env["gdnative_enabled"]: + in_files.append(build[2]) # Runtime + out_files.append(zip_dir.File(binary_name + ".side.wasm")) +elif env["threads_enabled"]: + in_files.append(build[2]) # Worker out_files.append(zip_dir.File(binary_name + ".worker.js")) zip_files = env.InstallAs(out_files, in_files) diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 71189cf697aeb..13afc8dd6a214 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -23,6 +23,7 @@ def get_opts(): # eval() can be a security concern, so it can be disabled. BoolVariable("javascript_eval", "Enable JavaScript eval interface", True), BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False), + BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), ] @@ -83,10 +84,8 @@ def configure(env): # LTO if env["use_lto"]: - env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"]) - env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"]) - env.Append(CCFLAGS=["-flto"]) - env.Append(LINKFLAGS=["-flto"]) + env.Append(CCFLAGS=["-flto=full"]) + env.Append(LINKFLAGS=["-flto=full"]) # Closure compiler if env["use_closure_compiler"]: @@ -133,6 +132,9 @@ def configure(env): if env["javascript_eval"]: env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) + if env["threads_enabled"] and env["gdnative_enabled"]: + raise Exception("Threads and GDNative support can't be both enabled due to WebAssembly limitations") + # Thread support (via SharedArrayBuffer). if env["threads_enabled"]: env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) @@ -144,14 +146,15 @@ def configure(env): else: env.Append(CPPDEFINES=["NO_THREADS"]) + if env["gdnative_enabled"]: + env.Append(CCFLAGS=["-s", "RELOCATABLE=1"]) + env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"]) + env.extra_suffix = ".gdnative" + env.extra_suffix + # Reduce code size by generating less support code (e.g. skip NodeJS support). env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) - # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to - # be linked explicitly. - env.Append(LIBS=["idbfs.js"]) - - env.Append(LINKFLAGS=["-s", "BINARYEN=1"]) + # Wrap the JavaScript support code around a closure named Godot. env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"]) # Allow increasing memory buffer size during runtime. This is efficient @@ -162,12 +165,14 @@ def configure(env): # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"]) + # Do not call main immediately when the support code is ready. env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"]) # Allow use to take control of swapping WebGL buffers. env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) - # callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS. + # callMain for manual start. env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) + # Add code that allow exiting runtime. env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index e5da449ebc8cb..aa65b61ca3474 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -85,36 +85,44 @@ class EditorHTTPServer : public Reference { // Wrong protocol ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); + const String cache_path = EditorSettings::get_singleton()->get_cache_dir(); const String basereq = "/tmp_js_export"; - String ctype = ""; + String filepath; + String ctype; if (req[1] == basereq + ".html") { - filepath += ".html"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "text/html"; } else if (req[1] == basereq + ".js") { - filepath += ".js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".audio.worklet.js") { - filepath += ".audio.worklet.js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".worker.js") { - filepath += ".worker.js"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/javascript"; } else if (req[1] == basereq + ".pck") { - filepath += ".pck"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/octet-stream"; } else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") { // Also allow serving the generated favicon for a smoother loading experience. if (req[1] == "/favicon.png") { filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"); } else { - filepath += ".png"; + filepath = basereq + ".png"; } ctype = "image/png"; + } else if (req[1] == basereq + ".side.wasm") { + filepath = cache_path.plus_file(req[1].get_file()); + ctype = "application/wasm"; } else if (req[1] == basereq + ".wasm") { - filepath += ".wasm"; + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/wasm"; - } else { + } else if (req[1].ends_with(".wasm")) { + filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous? + ctype = "application/wasm"; + } + if (filepath.empty() || !FileAccess::exists(filepath)) { String s = "HTTP/1.1 404 Not Found\r\n"; s += "Connection: Close\r\n"; s += "\r\n"; @@ -204,7 +212,33 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { Ref stop_icon; int menu_options; - void _fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug); + enum ExportMode { + EXPORT_MODE_NORMAL = 0, + EXPORT_MODE_THREADS = 1, + EXPORT_MODE_GDNATIVE = 2, + }; + + String _get_template_name(ExportMode p_mode, bool p_debug) const { + String name = "webassembly"; + switch (p_mode) { + case EXPORT_MODE_THREADS: + name += "_threads"; + break; + case EXPORT_MODE_GDNATIVE: + name += "_gdnative"; + break; + default: + break; + } + if (p_debug) { + name += "_debug.zip"; + } else { + name += "_release.zip"; + } + return name; + } + + void _fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug, const Vector p_shared_objects); private: Ref server; @@ -248,11 +282,15 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug) { +void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug, const Vector p_shared_objects) { String str_template = String::utf8(reinterpret_cast(p_html.ptr()), p_html.size()); String str_export; Vector lines = str_template.split("\n"); + String libs; + for (int i = 0; i < p_shared_objects.size(); i++) { + libs += "\"" + p_shared_objects[i].path.get_file() + "\","; + } for (int i = 0; i < lines.size(); i++) { @@ -261,6 +299,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Re current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name")); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false"); + current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); str_export += current_line + "\n"; } @@ -289,15 +328,21 @@ void EditorExportPlatformJavaScript::get_preset_features(const Refget("variant/export_type"); + if (mode == EXPORT_MODE_THREADS) { + r_features->push_back("threads"); + } else if (mode == EXPORT_MODE_GDNATIVE) { + r_features->push_back("wasm32"); + } } void EditorExportPlatformJavaScript::get_export_options(List *r_options) { r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "variant/export_type", PROPERTY_HINT_ENUM, "Regular,Threads,GDNative"), 0)); // Export type. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true)); @@ -322,11 +367,11 @@ bool EditorExportPlatformJavaScript::can_export(const Ref &p String err; bool valid = false; + ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); // Look for export templates (first official, and if defined custom templates). - - bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err); - bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err); + bool dvalid = exists_export_template(_get_template_name(mode, true), &err); + bool rvalid = exists_export_template(_get_template_name(mode, false), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -380,10 +425,8 @@ Error EditorExportPlatformJavaScript::export_project(const Refget("variant/export_type"); + template_path = find_export_template(_get_template_name(mode, p_debug)); } if (!DirAccess::exists(p_path.get_base_dir())) { @@ -395,12 +438,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref shared_objects; String pck_path = p_path.get_basename() + ".pck"; - Error error = save_pack(p_preset, pck_path); + Error error = save_pack(p_preset, pck_path, &shared_objects); if (error != OK) { EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); return error; } + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file()); + error = da->copy(shared_objects[i].path, dst); + if (error != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); + memdelete(da); + return error; + } + } + memdelete(da); FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -441,7 +496,7 @@ Error EditorExportPlatformJavaScript::export_project(const Refget_len()); f->get_buffer(buf.ptrw(), buf.size()); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug); + _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, shared_objects); f = FileAccess::open(p_path, FileAccess::WRITE); if (!f) { @@ -592,6 +651,7 @@ Error EditorExportPlatformJavaScript::run(const Ref &p_prese DirAccess::remove_file_or_error(basepath + ".audio.worklet.js"); DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".png"); + DirAccess::remove_file_or_error(basepath + ".side.wasm"); DirAccess::remove_file_or_error(basepath + ".wasm"); DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png")); return err; diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index 7ebda3ad39c20..0ba684971565f 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -48,7 +48,7 @@ extern void godot_audio_capture_stop(); typedef int32_t GodotAudioState[4]; extern void godot_audio_worklet_create(int p_channels); extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state); -extern void godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value); +extern int godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value); extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx); extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout); diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h index 57dc4f0d9f584..a270590f5cd6f 100644 --- a/platform/javascript/http_request.h +++ b/platform/javascript/http_request.h @@ -47,7 +47,7 @@ typedef enum { extern int godot_xhr_new(); extern void godot_xhr_reset(int p_xhr_id); -extern bool godot_xhr_free(int p_xhr_id); +extern void godot_xhr_free(int p_xhr_id); extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = NULL, const char *p_password = NULL); diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 86ef15d9f192e..c977c7c1a1855 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -74,7 +74,7 @@ void main_loop_callback() { } } -int main(int argc, char *argv[]) { +extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) { // Set locale char locale_ptr[16]; godot_js_config_locale_get(locale_ptr, sizeof(locale_ptr)); diff --git a/platform/javascript/javascript_runtime.cpp b/platform/javascript/javascript_runtime.cpp new file mode 100644 index 0000000000000..bfe9fbd1bc75d --- /dev/null +++ b/platform/javascript/javascript_runtime.cpp @@ -0,0 +1,35 @@ +/*************************************************************************/ +/* javascript_runtime.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +extern int godot_js_main(int argc, char *argv[]); + +int main(int argc, char *argv[]) { + return godot_js_main(argc, argv); +} diff --git a/platform/javascript/js/dynlink.pre.js b/platform/javascript/js/dynlink.pre.js new file mode 100644 index 0000000000000..34bc371ea9eb1 --- /dev/null +++ b/platform/javascript/js/dynlink.pre.js @@ -0,0 +1 @@ +Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []); diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 74153b672acaa..4b8a7dde69d6b 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -34,6 +34,7 @@ const Engine = (function () { this.onExecute = null; this.onExit = null; this.persistentPaths = ['/userfs']; + this.gdnativeLibs = []; } Engine.prototype.init = /** @param {string=} basePath */ function (basePath) { @@ -58,6 +59,10 @@ const Engine = (function () { initPromise = new Promise(function (resolve, reject) { config['locateFile'] = Utils.createLocateRewrite(loadPath); config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); + // Emscripten configuration. + config['thisProgram'] = me.executableName; + config['noExitRuntime'] = true; + config['dynamicLibraries'] = me.gdnativeLibs; Godot(config).then(function (module) { module['initFS'](me.persistentPaths).then(function (fs_err) { me.rtenv = module; @@ -119,9 +124,6 @@ const Engine = (function () { locale = navigator.languages ? navigator.languages[0] : navigator.language; locale = locale.split('.')[0]; } - // Emscripten configuration. - me.rtenv['thisProgram'] = me.executableName; - me.rtenv['noExitRuntime'] = true; // Godot configuration. me.rtenv['initConfig']({ 'resizeCanvasOnStart': me.resizeCanvasOnStart, @@ -249,6 +251,10 @@ const Engine = (function () { this.persistentPaths = persistentPaths; }; + Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) { + this.gdnativeLibs = gdnativeLibs; + }; + Engine.prototype.requestQuit = function () { if (this.rtenv) { this.rtenv['request_quit'](); @@ -277,6 +283,7 @@ const Engine = (function () { Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; + Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries; Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; return Engine; }()); diff --git a/platform/javascript/js/engine/utils.js b/platform/javascript/js/engine/utils.js index d0fca4e1cbfd3..9273bbad42e65 100644 --- a/platform/javascript/js/engine/utils.js +++ b/platform/javascript/js/engine/utils.js @@ -8,6 +8,8 @@ const Utils = { // eslint-disable-line no-unused-vars return `${execName}.audio.worklet.js`; } else if (path.endsWith('.js')) { return `${execName}.js`; + } else if (path.endsWith('.side.wasm')) { + return `${execName}.side.wasm`; } else if (path.endsWith('.wasm')) { return `${execName}.wasm`; } diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index 0c1f477f340cd..416e987513c58 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -137,6 +137,7 @@ const GodotAudio = { }, }, + godot_audio_is_available__sig: 'i', godot_audio_is_available__proxy: 'sync', godot_audio_is_available: function () { if (!(window.AudioContext || window.webkitAudioContext)) { @@ -145,12 +146,14 @@ const GodotAudio = { return 1; }, + godot_audio_init__sig: 'iiiii', godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { const statechange = GodotRuntime.get_func(p_state_change); const latencyupdate = GodotRuntime.get_func(p_latency_update); return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); }, + godot_audio_resume__sig: 'v', godot_audio_resume: function () { if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { GodotAudio.ctx.resume(); @@ -158,6 +161,7 @@ const GodotAudio = { }, godot_audio_capture_start__proxy: 'sync', + godot_audio_capture_start__sig: 'v', godot_audio_capture_start: function () { if (GodotAudio.input) { return; // Already started. @@ -168,6 +172,7 @@ const GodotAudio = { }, godot_audio_capture_stop__proxy: 'sync', + godot_audio_capture_stop__sig: 'v', godot_audio_capture_stop: function () { if (GodotAudio.input) { const tracks = GodotAudio.input['mediaStream']['getTracks'](); @@ -241,10 +246,12 @@ const GodotAudioWorklet = { }, }, + godot_audio_worklet_create__sig: 'vi', godot_audio_worklet_create: function (channels) { GodotAudioWorklet.create(channels); }, + godot_audio_worklet_start__sig: 'viiiii', godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); @@ -252,15 +259,18 @@ const GodotAudioWorklet = { GodotAudioWorklet.start(in_buffer, out_buffer, state); }, + godot_audio_worklet_state_wait__sig: 'iiii', godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) { Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); return Atomics.load(HEAP32, (p_state >> 2) + p_idx); }, + godot_audio_worklet_state_add__sig: 'iiii', godot_audio_worklet_state_add: function (p_state, p_idx, p_value) { return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); }, + godot_audio_worklet_state_get__sig: 'iii', godot_audio_worklet_state_get: function (p_state, p_idx) { return Atomics.load(HEAP32, (p_state >> 2) + p_idx); }, @@ -330,10 +340,12 @@ const GodotAudioScript = { }, }, + godot_audio_script_create__sig: 'iii', godot_audio_script_create: function (buffer_length, channel_count) { return GodotAudioScript.create(buffer_length, channel_count); }, + godot_audio_script_start__sig: 'viiiii', godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { const onprocess = GodotRuntime.get_func(p_cb); GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess); diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index 9651b48952d55..800d6f414f846 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -280,6 +280,7 @@ const GodotDisplay = { window_icon: '', }, + godot_js_display_is_swap_ok_cancel__sig: 'i', godot_js_display_is_swap_ok_cancel: function () { const win = (['Windows', 'Win64', 'Win32', 'WinCE']); const plat = navigator.platform || ''; @@ -289,10 +290,12 @@ const GodotDisplay = { return 0; }, + godot_js_display_alert__sig: 'vi', godot_js_display_alert: function (p_text) { window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert }, + godot_js_display_pixel_ratio_get__sig: 'f', godot_js_display_pixel_ratio_get: function () { return window.devicePixelRatio || 1; }, @@ -300,14 +303,17 @@ const GodotDisplay = { /* * Canvas */ + godot_js_display_canvas_focus__sig: 'v', godot_js_display_canvas_focus: function () { GodotConfig.canvas.focus(); }, + godot_js_display_canvas_is_focused__sig: 'i', godot_js_display_canvas_is_focused: function () { return document.activeElement === GodotConfig.canvas; }, + godot_js_display_canvas_bounding_rect_position_get__sig: 'vii', godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) { const brect = GodotConfig.canvas.getBoundingClientRect(); GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); @@ -317,6 +323,7 @@ const GodotDisplay = { /* * Touchscreen */ + godot_js_display_touchscreen_is_available__sig: 'i', godot_js_display_touchscreen_is_available: function () { return 'ontouchstart' in window; }, @@ -324,6 +331,7 @@ const GodotDisplay = { /* * Clipboard */ + godot_js_display_clipboard_set__sig: 'ii', godot_js_display_clipboard_set: function (p_text) { const text = GodotRuntime.parseString(p_text); if (!navigator.clipboard || !navigator.clipboard.writeText) { @@ -336,6 +344,7 @@ const GodotDisplay = { return 0; }, + godot_js_display_clipboard_get__sig: 'ii', godot_js_display_clipboard_get: function (callback) { const func = GodotRuntime.get_func(callback); try { @@ -354,6 +363,7 @@ const GodotDisplay = { /* * Window */ + godot_js_display_window_request_fullscreen__sig: 'v', godot_js_display_window_request_fullscreen: function () { const canvas = GodotConfig.canvas; (canvas.requestFullscreen || canvas.msRequestFullscreen @@ -362,10 +372,12 @@ const GodotDisplay = { ).call(canvas); }, + godot_js_display_window_title_set__sig: 'vi', godot_js_display_window_title_set: function (p_data) { document.title = GodotRuntime.parseString(p_data); }, + godot_js_display_window_icon_set__sig: 'vii', godot_js_display_window_icon_set: function (p_ptr, p_len) { let link = document.getElementById('-gd-engine-icon'); if (link === null) { @@ -386,6 +398,7 @@ const GodotDisplay = { /* * Cursor */ + godot_js_display_cursor_set_visible__sig: 'vi', godot_js_display_cursor_set_visible: function (p_visible) { const visible = p_visible !== 0; if (visible === GodotDisplayCursor.visible) { @@ -399,14 +412,17 @@ const GodotDisplay = { } }, + godot_js_display_cursor_is_hidden__sig: 'i', godot_js_display_cursor_is_hidden: function () { return !GodotDisplayCursor.visible; }, + godot_js_display_cursor_set_shape__sig: 'vi', godot_js_display_cursor_set_shape: function (p_string) { GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string)); }, + godot_js_display_cursor_set_custom_shape__sig: 'viiiii', godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { const shape = GodotRuntime.parseString(p_shape); const old_shape = GodotDisplayCursor.cursors[shape]; @@ -432,6 +448,7 @@ const GodotDisplay = { /* * Listeners */ + godot_js_display_notification_cb__sig: 'viiiii', godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { const canvas = GodotConfig.canvas; const func = GodotRuntime.get_func(callback); @@ -443,6 +460,7 @@ const GodotDisplay = { }); }, + godot_js_display_paste_cb__sig: 'vi', godot_js_display_paste_cb: function (callback) { const func = GodotRuntime.get_func(callback); GodotDisplayListeners.add(window, 'paste', function (evt) { @@ -453,6 +471,7 @@ const GodotDisplay = { }, false); }, + godot_js_display_drop_files_cb__sig: 'vi', godot_js_display_drop_files_cb: function (callback) { const func = GodotRuntime.get_func(callback); const dropFiles = function (files) { diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js index f39fed04a83de..12edc8e73324d 100644 --- a/platform/javascript/js/libs/library_godot_editor_tools.js +++ b/platform/javascript/js/libs/library_godot_editor_tools.js @@ -30,6 +30,7 @@ const GodotEditorTools = { godot_js_editor_download_file__deps: ['$FS'], + godot_js_editor_download_file__sig: 'viii', godot_js_editor_download_file: function (p_path, p_name, p_mime) { const path = GodotRuntime.parseString(p_path); const name = GodotRuntime.parseString(p_name); diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js index 33ff23172676c..1a2440dd24e5a 100644 --- a/platform/javascript/js/libs/library_godot_eval.js +++ b/platform/javascript/js/libs/library_godot_eval.js @@ -30,6 +30,7 @@ const GodotEval = { godot_js_eval__deps: ['$GodotRuntime'], + godot_js_eval__sig: 'iiiiiii', godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { const js_code = GodotRuntime.parseString(p_js); let eval_ret = null; diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js index 2b9aa88208fc2..d4468bd5aae3e 100644 --- a/platform/javascript/js/libs/library_godot_http_request.js +++ b/platform/javascript/js/libs/library_godot_http_request.js @@ -50,6 +50,7 @@ const GodotHTTPRequest = { }, }, + godot_xhr_new__sig: 'i', godot_xhr_new: function () { const newId = GodotHTTPRequest.getUnusedRequestId(); GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); @@ -57,30 +58,36 @@ const GodotHTTPRequest = { return newId; }, + godot_xhr_reset__sig: 'vi', godot_xhr_reset: function (xhrId) { GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); }, + godot_xhr_free__sig: 'vi', godot_xhr_free: function (xhrId) { GodotHTTPRequest.requests[xhrId].abort(); GodotHTTPRequest.requests[xhrId] = null; }, + godot_xhr_open__sig: 'viiiii', godot_xhr_open: function (xhrId, method, url, p_user, p_password) { const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); }, + godot_xhr_set_request_header__sig: 'viii', godot_xhr_set_request_header: function (xhrId, header, value) { GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); }, + godot_xhr_send_null__sig: 'vi', godot_xhr_send_null: function (xhrId) { GodotHTTPRequest.requests[xhrId].send(); }, + godot_xhr_send_string__sig: 'vii', godot_xhr_send_string: function (xhrId, strPtr) { if (!strPtr) { GodotRuntime.error('Failed to send string per XHR: null pointer'); @@ -89,6 +96,7 @@ const GodotHTTPRequest = { GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr)); }, + godot_xhr_send_data__sig: 'viii', godot_xhr_send_data: function (xhrId, ptr, len) { if (!ptr) { GodotRuntime.error('Failed to send data per XHR: null pointer'); @@ -101,23 +109,28 @@ const GodotHTTPRequest = { GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); }, + godot_xhr_abort__sig: 'vi', godot_xhr_abort: function (xhrId) { GodotHTTPRequest.requests[xhrId].abort(); }, + godot_xhr_get_status__sig: 'ii', godot_xhr_get_status: function (xhrId) { return GodotHTTPRequest.requests[xhrId].status; }, + godot_xhr_get_ready_state__sig: 'ii', godot_xhr_get_ready_state: function (xhrId) { return GodotHTTPRequest.requests[xhrId].readyState; }, + godot_xhr_get_response_headers_length__sig: 'ii', godot_xhr_get_response_headers_length: function (xhrId) { const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); return headers === null ? 0 : GodotRuntime.strlen(headers); }, + godot_xhr_get_response_headers__sig: 'viii', godot_xhr_get_response_headers: function (xhrId, dst, len) { const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); if (str === null) { @@ -126,11 +139,13 @@ const GodotHTTPRequest = { GodotRuntime.stringToHeap(str, dst, len); }, + godot_xhr_get_response_length__sig: 'ii', godot_xhr_get_response_length: function (xhrId) { const body = GodotHTTPRequest.requests[xhrId].response; return body === null ? 0 : body.byteLength; }, + godot_xhr_get_response__sig: 'viii', godot_xhr_get_response: function (xhrId, dst, len) { let buf = GodotHTTPRequest.requests[xhrId].response; if (buf === null) { diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 44b104922e452..260cfbf97fbea 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -75,14 +75,17 @@ const GodotConfig = { }, }, + godot_js_config_canvas_id_get__sig: 'vii', godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max); }, + godot_js_config_locale_get__sig: 'vii', godot_js_config_locale_get: function (p_ptr, p_ptr_max) { GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); }, + godot_js_config_is_resize_on_start__sig: 'i', godot_js_config_is_resize_on_start: function () { return GodotConfig.resize_on_start ? 1 : 0; }, @@ -239,19 +242,23 @@ const GodotOS = { }, }, + godot_js_os_finish_async__sig: 'vi', godot_js_os_finish_async: function (p_callback) { const func = GodotRuntime.get_func(p_callback); GodotOS.finish_async(func); }, + godot_js_os_request_quit_cb__sig: 'vi', godot_js_os_request_quit_cb: function (p_callback) { GodotOS.request_quit = GodotRuntime.get_func(p_callback); }, + godot_js_os_fs_is_persistent__sig: 'i', godot_js_os_fs_is_persistent: function () { return GodotFS.is_persistent(); }, + godot_js_os_fs_sync__sig: 'vi', godot_js_os_fs_sync: function (callback) { const func = GodotRuntime.get_func(callback); GodotOS._fs_sync_promise = GodotFS.sync(); @@ -260,6 +267,7 @@ const GodotOS = { }); }, + godot_js_os_execute__sig: 'ii', godot_js_os_execute: function (p_json) { const json_args = GodotRuntime.parseString(p_json); const args = JSON.parse(json_args); @@ -270,6 +278,7 @@ const GodotOS = { return 1; }, + godot_js_os_shell_open__sig: 'vi', godot_js_os_shell_open: function (p_uri) { window.open(GodotRuntime.parseString(p_uri), '_blank'); }, diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index e9132787ddb4f..f760dab6b1896 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -42,6 +42,7 @@ #include "servers/visual/visual_server_wrap_mt.h" #endif +#include #include #include #include @@ -1070,12 +1071,24 @@ int OS_JavaScript::get_process_id() const { bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") + if (p_feature == "HTML5" || p_feature == "web") { return true; + } #ifdef JAVASCRIPT_EVAL_ENABLED - if (p_feature == "JavaScript") + if (p_feature == "JavaScript") { + return true; + } +#endif +#ifndef NO_THREADS + if (p_feature == "threads") { + return true; + } +#endif +#if WASM_GDNATIVE + if (p_feature == "wasm32") { return true; + } #endif return false; @@ -1206,6 +1219,13 @@ bool OS_JavaScript::is_userfs_persistent() const { return idb_available; } +Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + String path = p_path.get_file(); + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror()); + return OK; +} + OS_JavaScript *OS_JavaScript::get_singleton() { return static_cast(OS::get_singleton()); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 75854e463262c..96febed3bc55e 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -185,7 +185,7 @@ class OS_JavaScript : public OS_Unix { virtual int get_power_percent_left(); virtual bool is_userfs_persistent() const; - + Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path); OS_JavaScript(); };