From 4ad95cc0399cd8cad6044dee857280943e66822c Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 5 Dec 2020 00:43:02 +0100 Subject: [PATCH] [HTML5] EditorRunNative works with GDNative. This "breaks" our loading bar logic (libraries are not counted). Fixing it is non trivial and probably deserves investigating a different strategy. --- misc/dist/html/full-size.html | 2 + platform/javascript/export/export.cpp | 63 ++++++++++++++++++------- platform/javascript/js/dynlink.pre.js | 2 +- platform/javascript/js/engine/engine.js | 7 +++ platform/javascript/os_javascript.cpp | 8 ++++ platform/javascript/os_javascript.h | 2 +- 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html index 8090eb4feb2..942e0123bb6 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 GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS]; const INDETERMINATE_STATUS_STEP_MS = 100; const FULL_WINDOW = $GODOT_FULL_WINDOW; @@ -267,6 +268,7 @@ $GODOT_HEAD_INCLUDE } else { setStatusMode('indeterminate'); engine.setCanvas(canvas); + engine.setGDNativeLibraries(GDNATIVE_LIBS); engine.startGame(EXECUTABLE_NAME, MAIN_PACK).then(() => { setStatusMode('hidden'); initializing = false; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index e5da449ebc8..87a48ba7d38 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"; @@ -204,7 +212,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); + 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 +256,15 @@ public: ~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 +273,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"; } @@ -297,7 +310,6 @@ void EditorExportPlatformJavaScript::get_export_options(List *r_op 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)); @@ -395,12 +407,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 +465,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 +620,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 6ac993bad09..34bc371ea9e 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 f0abdff640c..4b8a7dde69d 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 e9132787ddb..6346903cbfc 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 @@ -1206,6 +1207,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 75854e46326..96febed3bc5 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -185,7 +185,7 @@ public: 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(); };