diff options
Diffstat (limited to 'platform/javascript')
22 files changed, 357 insertions, 100 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 627ae778b1..7a8005fe30 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -12,13 +12,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", @@ -29,12 +24,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", @@ -61,10 +92,19 @@ 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")) +if env["tools"]: + in_files.append("#misc/dist/html/logo.svg") + out_files.append(zip_dir.File("logo.svg")) + in_files.append("#icon.png") + out_files.append(zip_dir.File("favicon.png")) + zip_files = env.InstallAs(out_files, in_files) env.Zip( "#bin/godot", diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 8d781703ed..a063718a0c 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -53,8 +53,7 @@ void JavaScriptToolsEditorPlugin::initialize() { } JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { - Variant v; - add_tool_menu_item("Download Project Source", this, "_download_zip", v); + add_tool_menu_item("Download Project Source", callable_mp(this, &JavaScriptToolsEditorPlugin::_download_zip)); } void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { @@ -73,10 +72,6 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip"); } -void JavaScriptToolsEditorPlugin::_bind_methods() { - ClassDB::bind_method("_download_zip", &JavaScriptToolsEditorPlugin::_download_zip); -} - void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { FileAccess *f = FileAccess::open(p_path, FileAccess::READ); if (!f) { diff --git a/platform/javascript/api/javascript_tools_editor_plugin.h b/platform/javascript/api/javascript_tools_editor_plugin.h index cc09fa4cd3..df1197139c 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.h +++ b/platform/javascript/api/javascript_tools_editor_plugin.h @@ -41,10 +41,6 @@ class JavaScriptToolsEditorPlugin : public EditorPlugin { private: void _zip_file(String p_path, String p_base_path, zipFile p_zip); void _zip_recursive(String p_path, String p_base_path, zipFile p_zip); - -protected: - static void _bind_methods(); - void _download_zip(Variant p_v); public: diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index dd982bc3a8..78fbed6d0f 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -189,7 +189,9 @@ Error AudioDriverJavaScript::capture_start() { lock(); input_buffer_init(buffer_length); unlock(); - godot_audio_capture_start(); + if (godot_audio_capture_start()) { + return FAILED; + } return OK; } diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 9f584d0899..d53c774e77 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,6 +1,8 @@ import os +import sys from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries +from methods import get_compiler_version from SCons.Util import WhereIs @@ -20,9 +22,17 @@ def get_opts(): from SCons.Variables import BoolVariable return [ + ("initial_memory", "Initial WASM memory (in MiB)", 16), + BoolVariable("use_assertions", "Use Emscripten runtime assertions", False), + BoolVariable("use_thinlto", "Use ThinLTO", False), + BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False), + BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False), + BoolVariable("use_lsan", "Use Emscripten leak sanitizer (LSAN)", False), + BoolVariable("use_safe_heap", "Use Emscripten SAFE_HEAP sanitizer", False), # 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), ] @@ -40,6 +50,9 @@ def get_flags(): def configure(env): + if not isinstance(env["initial_memory"], int): + print("Initial memory must be a valid integer") + sys.exit(255) ## Build type @@ -62,15 +75,18 @@ def configure(env): env.Append(CPPDEFINES=["DEBUG_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) env.Append(LINKFLAGS=["-O1", "-g"]) + env["use_assertions"] = True + + if env["use_assertions"]: env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"]) if env["tools"]: if not env["threads_enabled"]: - raise RuntimeError( - "Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option" - ) - # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY). - env.Append(LINKFLAGS=["-s", "TOTAL_MEMORY=33554432"]) + print("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option") + sys.exit(255) + if env["initial_memory"] < 32: + print("Editor build requires at least 32MiB of initial memory. Forcing it.") + env["initial_memory"] = 32 elif env["builtin_icu"]: env.Append(CCFLAGS=["-frtti"]) else: @@ -80,15 +96,32 @@ def configure(env): # Don't use dynamic_cast, necessary with no-rtti. env.Append(CPPDEFINES=["NO_SAFE_CAST"]) + env.Append(LINKFLAGS=["-s", "INITIAL_MEMORY=%sMB" % env["initial_memory"]]) + ## Copy env variables. env["ENV"] = os.environ # 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"]) + if env["use_thinlto"]: + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["use_lto"]: + env.Append(CCFLAGS=["-flto=full"]) + env.Append(LINKFLAGS=["-flto=full"]) + + # Sanitizers + if env["use_ubsan"]: + env.Append(CCFLAGS=["-fsanitize=undefined"]) + env.Append(LINKFLAGS=["-fsanitize=undefined"]) + if env["use_asan"]: + env.Append(CCFLAGS=["-fsanitize=address"]) + env.Append(LINKFLAGS=["-fsanitize=address"]) + if env["use_lsan"]: + env.Append(CCFLAGS=["-fsanitize=leak"]) + env.Append(LINKFLAGS=["-fsanitize=leak"]) + if env["use_safe_heap"]: + env.Append(CCFLAGS=["-s", "SAFE_HEAP=1"]) + env.Append(LINKFLAGS=["-s", "SAFE_HEAP=1"]) # Closure compiler if env["use_closure_compiler"]: @@ -135,6 +168,10 @@ def configure(env): if env["javascript_eval"]: env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) + if env["threads_enabled"] and env["gdnative_enabled"]: + print("Threads and GDNative support can't be both enabled due to WebAssembly limitations") + sys.exit(255) + # Thread support (via SharedArrayBuffer). if env["threads_enabled"]: env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) @@ -146,14 +183,19 @@ def configure(env): else: env.Append(CPPDEFINES=["NO_THREADS"]) + if env["gdnative_enabled"]: + major, minor, patch = get_compiler_version(env) + if major < 2 or (major == 2 and minor == 0 and patch < 10): + print("GDNative support requires emscripten >= 2.0.10, detected: %s.%s.%s" % (major, minor, patch)) + sys.exit(255) + 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 @@ -164,12 +206,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/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index af8800d565..92e13553fc 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -948,8 +948,8 @@ void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_wi last_width = p_size.x; last_height = p_size.y; double scale = godot_js_display_pixel_ratio_get(); - emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); - emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y); + emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); + emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index c3b7e0304e..37681b2484 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -37,16 +37,13 @@ #include "platform/javascript/logo.gen.h" #include "platform/javascript/run_icon.gen.h" -#define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip" -#define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip" - class EditorHTTPServer : public Reference { private: Ref<TCP_Server> server; Ref<StreamPeerTCP> connection; - uint64_t time; + uint64_t time = 0; uint8_t req_buf[4096]; - int req_pos; + int req_pos = 0; void _clear_client() { connection = Ref<StreamPeerTCP>(); @@ -85,36 +82,44 @@ public: // 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"; @@ -203,15 +208,40 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; Ref<ImageTexture> stop_icon; - int menu_options; - - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags); + int menu_options = 0; -private: Ref<EditorHTTPServer> server; - bool server_quit; + bool server_quit = false; Mutex server_lock; - Thread *server_thread; + Thread *server_thread = nullptr; + + 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<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects); static void _server_thread_poll(void *data); @@ -250,7 +280,7 @@ public: ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) { +void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) { String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); String str_export; Vector<String> lines = str_template.split("\n"); @@ -258,6 +288,10 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re String flags_json; gen_export_flags(flags, p_flags); flags_json = JSON::print(flags); + 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++) { String current_line = lines[i]; @@ -265,6 +299,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &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"); current_line = current_line.replace("$GODOT_ARGS", flags_json); str_export += current_line + "\n"; @@ -291,12 +326,19 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP r_features->push_back("etc2"); } } + ExportMode mode = (ExportMode)(int)p_preset->get("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<ExportOption> *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 @@ -320,11 +362,11 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const { bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { 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")); @@ -377,11 +419,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese template_path = template_path.strip_edges(); if (template_path == String()) { - if (p_debug) { - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); - } else { - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); - } + ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); + template_path = find_export_template(_get_template_name(mode, p_debug)); } if (!DirAccess::exists(p_path.get_base_dir())) { @@ -393,12 +432,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return ERR_FILE_NOT_FOUND; } + Vector<SharedObject> 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 = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -437,14 +488,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese if (!custom_html.empty()) { continue; } - _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); + _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); file = p_path.get_file(); } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; + } else if (file == "godot.worker.js") { file = p_path.get_file().get_basename() + ".worker.js"; + } else if (file == "godot.side.wasm") { + file = p_path.get_file().get_basename() + ".side.wasm"; + } else if (file == "godot.audio.worklet.js") { file = p_path.get_file().get_basename() + ".audio.worklet.js"; @@ -475,7 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese buf.resize(f->get_len()); f->get_buffer(buf.ptrw(), buf.size()); memdelete(f); - _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); + _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); f = FileAccess::open(p_path, FileAccess::WRITE); if (!f) { @@ -577,6 +632,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &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; @@ -625,7 +681,6 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { server.instance(); - server_quit = false; server_thread = Thread::create(_server_thread_poll, this); Ref<Image> img = memnew(Image(_javascript_logo)); @@ -642,8 +697,6 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { } else { stop_icon.instance(); } - - menu_options = 0; } EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index 7ebda3ad39..aeb234269e 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -41,14 +41,14 @@ extern int godot_audio_is_available(); extern int godot_audio_init(int p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float)); extern void godot_audio_resume(); -extern void godot_audio_capture_start(); +extern int godot_audio_capture_start(); extern void godot_audio_capture_stop(); // Worklet 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 54e98c1927..41e4749216 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 = nullptr, const char *p_password = nullptr); diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 2d28a63566..b4985a4f36 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -75,7 +75,7 @@ void main_loop_callback() { } /// When calling main, it is assumed FS is setup and synced. -int main(int argc, char *argv[]) { +extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) { os = new OS_JavaScript(); // We must override main when testing is enabled diff --git a/platform/javascript/javascript_runtime.cpp b/platform/javascript/javascript_runtime.cpp new file mode 100644 index 0000000000..bfe9fbd1bc --- /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 0000000000..34bc371ea9 --- /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 74153b672a..4b8a7dde69 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 d0fca4e1cb..9273bbad42 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 0c1f477f34..d01b8d887b 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -77,28 +77,37 @@ const GodotAudio = { create_input: function (callback) { if (GodotAudio.input) { - return; // Already started. + return 0; // Already started. } function gotMediaInput(stream) { - GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); - callback(GodotAudio.input); + try { + GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); + callback(GodotAudio.input); + } catch (e) { + GodotRuntime.error('Failed creaating input.', e); + } } - if (navigator.mediaDevices.getUserMedia) { + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ 'audio': true, }).then(gotMediaInput, function (e) { - GodotRuntime.print(e); + GodotRuntime.error('Error getting user media.', e); }); } else { if (!navigator.getUserMedia) { navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; } + if (!navigator.getUserMedia) { + GodotRuntime.error('getUserMedia not available.'); + return 1; + } navigator.getUserMedia({ 'audio': true, }, gotMediaInput, function (e) { GodotRuntime.print(e); }); } + return 0; }, close_async: function (resolve, reject) { @@ -137,6 +146,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 +155,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,16 +170,15 @@ const GodotAudio = { }, godot_audio_capture_start__proxy: 'sync', + godot_audio_capture_start__sig: 'i', godot_audio_capture_start: function () { - if (GodotAudio.input) { - return; // Already started. - } - GodotAudio.create_input(function (input) { + return GodotAudio.create_input(function (input) { input.connect(GodotAudio.driver.get_node()); }); }, 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 +252,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 +265,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 +346,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 9651b48952..800d6f414f 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 f39fed04a8..12edc8e733 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 33ff231726..1a2440dd24 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 2b9aa88208..d4468bd5aa 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 488753d704..260cfbf97f 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; }, @@ -200,7 +203,7 @@ const GodotFS = { } FS.mkdirTree(dir); } - FS.writeFile(path, new Uint8Array(buffer), { 'flags': 'wx+' }); + FS.writeFile(path, new Uint8Array(buffer)); }, }, }; @@ -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 80723d54fc..8c976da58e 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -31,7 +31,6 @@ #include "os_javascript.h" #include "core/debugger/engine_debugger.h" -#include "core/io/file_access_buffered_fa.h" #include "core/io/json.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" @@ -43,6 +42,7 @@ #include "modules/websocket/remote_debugger_peer_websocket.h" #endif +#include <dlfcn.h> #include <emscripten.h> #include <stdlib.h> @@ -51,7 +51,6 @@ // Lifecycle void OS_JavaScript::initialize() { OS_Unix::initialize_core(); - FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES); DisplayServerJavaScript::register_javascript_driver(); #ifdef MODULE_WEBSOCKET_ENABLED @@ -127,12 +126,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; @@ -187,6 +198,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_JavaScript *>(OS::get_singleton()); } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 03a3053367..6682f8fd85 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -87,6 +87,7 @@ public: String get_user_data_dir() const override; bool is_userfs_persistent() const override; + Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override; void resume_audio(); |