diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index 7d29b35f6148..85c5305b85ff 100644 --- a/misc/dist/html/full-size.html +++ b/misc/dist/html/full-size.html @@ -145,6 +145,7 @@ $GODOT_HEAD_INCLUDE const EXECUTABLE_NAME = '$GODOT_BASENAME'; const MAIN_PACK = '$GODOT_BASENAME.pck'; const EXTRA_ARGS = JSON.parse('$GODOT_ARGS'); + const GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS]; const INDETERMINATE_STATUS_STEP_MS = 100; const FULL_WINDOW = $GODOT_FULL_WINDOW; @@ -263,6 +264,7 @@ $GODOT_HEAD_INCLUDE } else { setStatusMode('indeterminate'); engine.setCanvas(canvas); + engine.setGDNativeLibraries(GDNATIVE_LIBS); engine.startGame(EXECUTABLE_NAME, MAIN_PACK, EXTRA_ARGS).then(() => { setStatusMode('hidden'); initializing = false; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index c3b7e0304ef7..4ef66f217baf 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -85,36 +85,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 + ".wasm") { - filepath += ".wasm"; + } else if (req[1] == basereq + ".side.wasm") { + filepath = cache_path.plus_file(req[1].get_file()); ctype = "application/wasm"; - } else { + } else if (req[1] == basereq + ".wasm") { + filepath = cache_path.plus_file(req[1].get_file()); + ctype = "application/wasm"; + } 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"; @@ -205,7 +213,7 @@ 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, int p_flags); + void _fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector p_shared_objects); private: Ref server; @@ -250,7 +258,7 @@ public: ~EditorExportPlatformJavaScript(); }; -void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug, int p_flags) { +void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Ref &p_preset, const String &p_name, bool p_debug, int p_flags, 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"); @@ -258,6 +266,10 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &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 +277,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"); current_line = current_line.replace("$GODOT_ARGS", flags_json); str_export += current_line + "\n"; @@ -393,12 +406,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 = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); @@ -437,14 +462,18 @@ 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, 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 +606,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/js/dynlink.pre.js b/platform/javascript/js/dynlink.pre.js index 6ac993bad090..34bc371ea9eb 100644 --- a/platform/javascript/js/dynlink.pre.js +++ b/platform/javascript/js/dynlink.pre.js @@ -1 +1 @@ -Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm']; +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 f0abdff640ca..4b8a7dde69d6 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) { @@ -61,6 +62,7 @@ const Engine = (function () { // 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; @@ -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/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 80723d54fc77..b3ef3fe57330 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -43,6 +43,7 @@ #include "modules/websocket/remote_debugger_peer_websocket.h" #endif +#include #include #include @@ -187,6 +188,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 03a305336777..6682f8fd853d 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();