diff options
Diffstat (limited to 'platform/javascript')
31 files changed, 661 insertions, 273 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 8d9ba82fd4..4827dc4627 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -37,6 +37,8 @@ for ext in env["JS_EXTERNS"]: build = [] if env["gdnative_enabled"]: build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] + if env["threads_enabled"]: + build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") # 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. @@ -58,7 +60,7 @@ if env["gdnative_enabled"]: 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]] + build = sys + [wasm[0]] else: build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] if env["threads_enabled"]: @@ -87,5 +89,5 @@ wrap_list = [ js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js") # Extra will be the thread worker, or the GDNative side, or None -extra = build[2] if len(build) > 2 else None +extra = build[2:] if len(build) > 2 else None env.CreateTemplateZip(js_wrapped, build[1], extra) diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 4190b24b8e..46a0a816bf 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -37,8 +37,8 @@ static JavaScript *javascript_eval; void register_javascript_api() { JavaScriptToolsEditorPlugin::initialize(); - GDREGISTER_VIRTUAL_CLASS(JavaScriptObject); - GDREGISTER_VIRTUAL_CLASS(JavaScript); + GDREGISTER_ABSTRACT_CLASS(JavaScriptObject); + GDREGISTER_ABSTRACT_CLASS(JavaScript); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); } diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index c68ac655a8..1507f32375 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -46,14 +46,14 @@ extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, co } static void _javascript_editor_init_callback() { - EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton()))); + EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin)); } void JavaScriptToolsEditorPlugin::initialize() { EditorNode::add_init_callback(_javascript_editor_init_callback); } -JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { +JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin() { add_tool_menu_item("Download Project Source", callable_mp(this, &JavaScriptToolsEditorPlugin::_download_zip)); } @@ -64,8 +64,8 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { } String resource_path = ProjectSettings::get_singleton()->get_resource_path(); - FileAccess *src_f; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + Ref<FileAccess> io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); // Name the downloaded ZIP file to contain the project name and download date for easier organization. // Replace characters not allowed (or risky) in Windows file names with safe characters. @@ -82,22 +82,22 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { const String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; _zip_recursive(resource_path, base_path, zip); zipClose(zip, nullptr); - FileAccess *f = FileAccess::open(output_path, FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Unable to create ZIP file."); - Vector<uint8_t> buf; - buf.resize(f->get_length()); - f->get_buffer(buf.ptrw(), buf.size()); - godot_js_os_download_buffer(buf.ptr(), buf.size(), output_name.utf8().get_data(), "application/zip"); + { + Ref<FileAccess> f = FileAccess::open(output_path, FileAccess::READ); + ERR_FAIL_COND_MSG(f.is_null(), "Unable to create ZIP file."); + Vector<uint8_t> buf; + buf.resize(f->get_length()); + f->get_buffer(buf.ptrw(), buf.size()); + godot_js_os_download_buffer(buf.ptr(), buf.size(), output_name.utf8().get_data(), "application/zip"); + } - f->close(); - memdelete(f); // Remove the temporary file since it was sent to the user's native filesystem as a download. DirAccess::remove_file_or_error(output_path); } void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - if (!f) { + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); + if (f.is_null()) { WARN_PRINT("Unable to open file for zipping: " + p_path); return; } @@ -105,8 +105,6 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z uint64_t len = f->get_length(); data.resize(len); f->get_buffer(data.ptrw(), len); - f->close(); - memdelete(f); String path = p_path.replace_first(p_base_path, ""); zipOpenNewFileInZip(p_zip, @@ -124,8 +122,8 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z } void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { - DirAccess *dir = DirAccess::open(p_path); - if (!dir) { + Ref<DirAccess> dir = DirAccess::open(p_path); + if (dir.is_null()) { WARN_PRINT("Unable to open directory for zipping: " + p_path); return; } diff --git a/platform/javascript/api/javascript_tools_editor_plugin.h b/platform/javascript/api/javascript_tools_editor_plugin.h index 08f10b01dc..cbf5f49497 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.h +++ b/platform/javascript/api/javascript_tools_editor_plugin.h @@ -46,7 +46,7 @@ private: public: static void initialize(); - JavaScriptToolsEditorPlugin(EditorNode *p_editor); + JavaScriptToolsEditorPlugin(); }; #else class JavaScriptToolsEditorPlugin { diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index b7b0b3ac96..807e2f936b 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -158,4 +158,4 @@ public: }; #endif -#endif +#endif // AUDIO_DRIVER_JAVASCRIPT_H diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index b6be44fbb2..a769260f01 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -48,11 +48,6 @@ def get_flags(): return [ ("tools", False), ("builtin_pcre2_with_jit", False), - # Disabling the mbedtls module reduces file size. - # The module has little use due to the limited networking functionality - # in this platform. For the available networking methods, the browser - # manages TLS. - ("module_mbedtls_enabled", False), ("vulkan", False), ] @@ -190,10 +185,6 @@ 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"]) @@ -206,12 +197,19 @@ def configure(env): 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)) + cc_version = get_compiler_version(env) + cc_semver = (int(cc_version["major"]), int(cc_version["minor"]), int(cc_version["patch"])) + if cc_semver < (2, 0, 10): + print("GDNative support requires emscripten >= 2.0.10, detected: %s.%s.%s" % cc_semver) + sys.exit(255) + + if env["threads_enabled"] and cc_semver < (3, 1, 14): + print("Threads and GDNative requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver) sys.exit(255) env.Append(CCFLAGS=["-s", "RELOCATABLE=1"]) env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"]) + # Weak symbols are broken upstream: https://github.com/emscripten-core/emscripten/issues/12819 + env.Append(CPPDEFINES=["ZSTD_HAVE_WEAK_SYMBOLS=0"]) env.extra_suffix = ".gdnative" + env.extra_suffix # Reduce code size by generating less support code (e.g. skip NodeJS support). diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index a0e1246c55..48f637fcfe 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "platform/javascript/display_server_javascript.h" +#include "display_server_javascript.h" #ifdef GLES3_ENABLED #include "drivers/gles3/rasterizer_gles3.h" #endif #include "platform/javascript/os_javascript.h" -#include "servers/rendering/rasterizer_dummy.h" +#include "servers/rendering/dummy/rasterizer_dummy.h" #include <emscripten.h> #include <png.h> @@ -83,7 +83,7 @@ void DisplayServerJavaScript::drop_files_js_callback(char **p_filev, int p_filec Variant *vp = &v; Variant ret; Callable::CallError ce; - ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + ds->drop_files_callback.callp((const Variant **)&vp, 1, ret, ce); } // JavaScript quit request callback. @@ -94,7 +94,7 @@ void DisplayServerJavaScript::request_quit_callback() { Variant *eventp = &event; Variant ret; Callable::CallError ce; - ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); + ds->window_event_callback.callp((const Variant **)&eventp, 1, ret, ce); } } @@ -236,7 +236,7 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { switch (p_shape) { case DisplayServer::CURSOR_ARROW: - return "auto"; + return "default"; case DisplayServer::CURSOR_IBEAM: return "text"; case DisplayServer::CURSOR_POINTING_HAND: @@ -244,9 +244,9 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape case DisplayServer::CURSOR_CROSS: return "crosshair"; case DisplayServer::CURSOR_WAIT: - return "progress"; - case DisplayServer::CURSOR_BUSY: return "wait"; + case DisplayServer::CURSOR_BUSY: + return "progress"; case DisplayServer::CURSOR_DRAG: return "grab"; case DisplayServer::CURSOR_CAN_DROP: @@ -270,7 +270,91 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape case DisplayServer::CURSOR_HELP: return "help"; default: - return "auto"; + return "default"; + } +} + +bool DisplayServerJavaScript::tts_is_speaking() const { + return godot_js_tts_is_speaking(); +} + +bool DisplayServerJavaScript::tts_is_paused() const { + return godot_js_tts_is_paused(); +} + +void DisplayServerJavaScript::update_voices_callback(int p_size, const char **p_voice) { + get_singleton()->voices.clear(); + for (int i = 0; i < p_size; i++) { + Vector<String> tokens = String::utf8(p_voice[i]).split(";", true, 2); + if (tokens.size() == 2) { + Dictionary voice_d; + voice_d["name"] = tokens[1]; + voice_d["id"] = tokens[1]; + voice_d["language"] = tokens[0]; + get_singleton()->voices.push_back(voice_d); + } + } +} + +Array DisplayServerJavaScript::tts_get_voices() const { + godot_js_tts_get_voices(update_voices_callback); + return voices; +} + +void DisplayServerJavaScript::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + if (p_interrupt) { + tts_stop(); + } + + if (p_text.is_empty()) { + tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id); + return; + } + + CharString string = p_text.utf8(); + utterance_ids[p_utterance_id] = string; + + godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerJavaScript::_js_utterance_callback); +} + +void DisplayServerJavaScript::tts_pause() { + godot_js_tts_pause(); +} + +void DisplayServerJavaScript::tts_resume() { + godot_js_tts_resume(); +} + +void DisplayServerJavaScript::tts_stop() { + for (const KeyValue<int, CharString> &E : utterance_ids) { + tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key); + } + utterance_ids.clear(); + godot_js_tts_stop(); +} + +void DisplayServerJavaScript::_js_utterance_callback(int p_event, int p_id, int p_pos) { + DisplayServerJavaScript *ds = (DisplayServerJavaScript *)DisplayServer::get_singleton(); + if (ds->utterance_ids.has(p_id)) { + int pos = 0; + if ((TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) { + // Convert position from UTF-8 to UTF-32. + const CharString &string = ds->utterance_ids[p_id]; + for (int i = 0; i < MIN(p_pos, string.length()); i++) { + uint8_t c = string[i]; + if ((c & 0xe0) == 0xc0) { + i += 1; + } else if ((c & 0xf0) == 0xe0) { + i += 2; + } else if ((c & 0xf8) == 0xf0) { + i += 3; + } + pos++; + } + } else if ((TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) { + ds->utterance_ids.erase(p_id); + } + ds->tts_post_utterance_event((TTSUtteranceEvent)p_event, p_id, pos); } } @@ -287,7 +371,7 @@ DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const { return cursor_shape; } -void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { +void DisplayServerJavaScript::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { if (p_cursor.is_valid()) { Ref<Texture2D> texture = p_cursor; Ref<AtlasTexture> atlas_texture = p_cursor; @@ -325,12 +409,13 @@ void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, Curso image = image->duplicate(); - if (atlas_texture.is_valid()) + if (atlas_texture.is_valid()) { image->crop_from_point( atlas_rect.position.x, atlas_rect.position.y, texture_size.width, texture_size.height); + } if (image->get_format() != Image::FORMAT_RGBA8) { image->convert(Image::FORMAT_RGBA8); @@ -501,7 +586,7 @@ void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_c Variant *eventp = &event; Variant ret; Callable::CallError ce; - ds->input_text_callback.call((const Variant **)&eventp, 1, ret, ce); + ds->input_text_callback.callp((const Variant **)&eventp, 1, ret, ce); // Insert key right to reach position. Input *input = Input::get_singleton(); Ref<InputEventKey> k; @@ -519,8 +604,8 @@ void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_c } } -void DisplayServerJavaScript::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) { - godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_multiline, p_cursor_start, p_cursor_end); +void DisplayServerJavaScript::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_type, p_cursor_start, p_cursor_end); } void DisplayServerJavaScript::virtual_keyboard_hide() { @@ -606,7 +691,7 @@ void DisplayServerJavaScript::send_window_event_callback(int p_notification) { Variant *eventp = &event; Variant ret; Callable::CallError ce; - ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); + ds->window_event_callback.callp((const Variant **)&eventp, 1, ret, ce); } } @@ -618,8 +703,9 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { ERR_FAIL_COND(icon->decompress() != OK); } if (icon->get_format() != Image::FORMAT_RGBA8) { - if (icon == p_icon) + if (icon == p_icon) { icon = icon->duplicate(); + } icon->convert(Image::FORMAT_RGBA8); } @@ -648,7 +734,7 @@ void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_eve Variant *evp = &ev; Variant ret; Callable::CallError ce; - cb.call((const Variant **)&evp, 1, ret, ce); + cb.callp((const Variant **)&evp, 1, ret, ce); } } @@ -753,6 +839,8 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const { //case FEATURE_ORIENTATION: case FEATURE_VIRTUAL_KEYBOARD: return godot_js_display_vk_available() != 0; + case FEATURE_TEXT_TO_SPEECH: + return godot_js_display_tts_available() != 0; default: return false; } @@ -891,8 +979,9 @@ Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) { - if (window_mode == p_mode) + if (window_mode == p_mode) { return; + } switch (p_mode) { case WINDOW_MODE_WINDOWED: { diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index b50956d91c..fb7f5d02a8 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -55,6 +55,8 @@ private: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE webgl_ctx = 0; #endif + HashMap<int, CharString> utterance_ids; + WindowMode window_mode = WINDOW_MODE_WINDOWED; ObjectID window_attached_instance_id = {}; @@ -66,6 +68,8 @@ private: String clipboard; Point2 touches[32]; + Array voices; + char canvas_id[256] = { 0 }; bool cursor_inside_canvas = true; CursorShape cursor_shape = CURSOR_ARROW; @@ -89,6 +93,7 @@ private: static void vk_input_text_callback(const char *p_text, int p_cursor); static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); void process_joypads(); + static void _js_utterance_callback(int p_event, int p_id, int p_pos); static Vector<String> get_rendering_drivers_func(); static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); @@ -97,6 +102,7 @@ private: static void request_quit_callback(); static void window_blur_callback(); + static void update_voices_callback(int p_size, const char **p_voice); static void update_clipboard_callback(const char *p_text); static void send_window_event_callback(int p_notification); static void drop_files_js_callback(char **p_filev, int p_filec); @@ -115,10 +121,20 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; + // tts + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual Array tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + // cursor virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; - virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; + virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; // mouse virtual void mouse_set_mode(MouseMode p_mode) override; @@ -141,7 +157,7 @@ public: virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; + virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), VirtualKeyboardType p_type = KEYBOARD_TYPE_DEFAULT, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; virtual void virtual_keyboard_hide() override; // windows diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index 4dad2d5204..3cb1d75e52 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -52,10 +52,10 @@ def create_template_zip(env, js, wasm, extra): ] # GDNative/Threads specific if env["gdnative_enabled"]: - in_files.append(extra) # Runtime + in_files.append(extra.pop()) # Runtime out_files.append(zip_dir.File(binary_name + ".side.wasm")) - elif env["threads_enabled"]: - in_files.append(extra) # Worker + if env["threads_enabled"]: + in_files.append(extra.pop()) # Worker out_files.append(zip_dir.File(binary_name + ".worker.js")) service_worker = "#misc/dist/html/service-worker.js" diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 825c1b6638..ea236f62f7 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -30,6 +30,7 @@ #include "export.h" +#include "editor/editor_settings.h" #include "export_plugin.h" void register_javascript_exporter() { diff --git a/platform/javascript/export/export.h b/platform/javascript/export/export.h index 41cc66cfb8..29c335ed0e 100644 --- a/platform/javascript/export/export.h +++ b/platform/javascript/export/export.h @@ -33,4 +33,4 @@ void register_javascript_exporter(); -#endif +#endif // JAVASCRIPT_EXPORT_H diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp index 8a1360e421..0bdee11018 100644 --- a/platform/javascript/export/export_plugin.cpp +++ b/platform/javascript/export/export_plugin.cpp @@ -31,20 +31,20 @@ #include "export_plugin.h" #include "core/config/project_settings.h" -#include "editor/editor_node.h" +#include "editor/editor_settings.h" Error EditorExportPlatformJavaScript::_extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa) { - FileAccess *src_f = nullptr; - zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + Ref<FileAccess> io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io); if (!pkg) { - EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + p_template); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not open template for export: \"%s\"."), p_template)); return ERR_FILE_NOT_FOUND; } if (unzGoToFirstFile(pkg) != UNZ_OK) { - EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + p_template); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Invalid export template: \"%s\"."), p_template)); unzClose(pkg); return ERR_FILE_CORRUPT; } @@ -57,6 +57,11 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template String file = String::utf8(fname); + // Skip folders. + if (file.ends_with("/")) { + continue; + } + // Skip service worker and offline page if not exporting pwa. if (!pwa && (file == "godot.service.worker.js" || file == "godot.offline.html")) { continue; @@ -71,14 +76,13 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template //write String dst = p_dir.plus_file(file.replace("godot", p_name)); - FileAccess *f = FileAccess::open(dst, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst); + Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst)); unzClose(pkg); return ERR_FILE_CANT_WRITE; } f->store_buffer(data.ptr(), data.size()); - memdelete(f); } while (unzGoToNextFile(pkg) == UNZ_OK); unzClose(pkg); @@ -86,17 +90,16 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template } Error EditorExportPlatformJavaScript::_write_or_error(const uint8_t *p_content, int p_size, String p_path) { - FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); + Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), p_path)); return ERR_FILE_CANT_WRITE; } f->store_buffer(p_content, p_size); - memdelete(f); return OK; } -void EditorExportPlatformJavaScript::_replace_strings(Map<String, String> p_replaces, Vector<uint8_t> &r_template) { +void EditorExportPlatformJavaScript::_replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template) { String str_template = String::utf8(reinterpret_cast<const char *>(r_template.ptr()), r_template.size()); String out; Vector<String> lines = str_template.split("\n"); @@ -148,7 +151,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re // Replaces HTML string const String str_config = Variant(config).to_json_string(); const String custom_head_include = p_preset->get("html/head_include"); - Map<String, String> replaces; + HashMap<String, String> replaces; replaces["$GODOT_URL"] = p_name + ".js"; replaces["$GODOT_PROJECT_NAME"] = ProjectSettings::get_singleton()->get_setting("application/config/name"); replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include; @@ -166,7 +169,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c icon.instantiate(); const Error err = ImageLoader::load_image(p_icon, icon); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + p_icon); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not read file: \"%s\"."), p_icon)); return err; } if (icon->get_width() != p_size || icon->get_height() != p_size) { @@ -178,7 +181,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c } const Error err = icon->save_png(icon_dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + icon_dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not write file: \"%s\"."), icon_dest)); return err; } Dictionary icon_dict; @@ -199,7 +202,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & const String dir = p_path.get_base_dir(); const String name = p_path.get_file().get_basename(); const ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); - Map<String, String> replaces; + HashMap<String, String> replaces; replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; @@ -213,7 +216,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & cache_files.push_back(name + ".icon.png"); cache_files.push_back(name + ".apple-touch-icon.png"); } - if (mode == EXPORT_MODE_THREADS) { + if (mode & EXPORT_MODE_THREADS) { cache_files.push_back(name + ".worker.js"); cache_files.push_back(name + ".audio.worklet.js"); } @@ -223,7 +226,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & Array opt_cache_files; opt_cache_files.push_back(name + ".wasm"); opt_cache_files.push_back(name + ".pck"); - if (mode == EXPORT_MODE_GDNATIVE) { + if (mode & EXPORT_MODE_GDNATIVE) { opt_cache_files.push_back(name + ".side.wasm"); for (int i = 0; i < p_shared_objects.size(); i++) { opt_cache_files.push_back(p_shared_objects[i].path.get_file()); @@ -234,15 +237,13 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & const String sw_path = dir.plus_file(name + ".service.worker.js"); Vector<uint8_t> sw; { - FileAccess *f = FileAccess::open(sw_path, FileAccess::READ); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path); + Ref<FileAccess> f = FileAccess::open(sw_path, FileAccess::READ); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), sw_path)); return ERR_FILE_CANT_READ; } sw.resize(f->get_length()); f->get_buffer(sw.ptrw(), sw.size()); - memdelete(f); - f = nullptr; } _replace_strings(replaces, sw); Error err = _write_or_error(sw.ptr(), sw.size(), dir.plus_file(name + ".service.worker.js")); @@ -253,11 +254,11 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & // Custom offline page const String offline_page = p_preset->get("progressive_web_app/offline_page"); if (!offline_page.is_empty()) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); const String offline_dest = dir.plus_file(name + ".offline.html"); err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + offline_dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), offline_dest)); return err; } } @@ -302,7 +303,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & return OK; } -void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { +void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { if (p_preset->get("vram_texture_compression/for_desktop")) { r_features->push_back("s3tc"); } @@ -317,9 +318,10 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP } } ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); - if (mode == EXPORT_MODE_THREADS) { + if (mode & EXPORT_MODE_THREADS) { r_features->push_back("threads"); - } else if (mode == EXPORT_MODE_GDNATIVE) { + } + if (mode & EXPORT_MODE_GDNATIVE) { r_features->push_back("wasm32"); } } @@ -360,7 +362,16 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const { return logo; } -bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { +bool EditorExportPlatformJavaScript::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { +#ifndef DEV_ENABLED + // We don't provide export templates for the HTML5 platform currently as there + // is no suitable renderer to use with them. So we forbid exporting and tell + // users why. This is skipped in DEV_ENABLED so that contributors can still test + // the pipeline once we start having WebGL or WebGPU support. + r_error = "The HTML5 platform is currently not supported in Godot 4.0, as there is no suitable renderer for it.\n"; + return false; +#endif + String err; bool valid = false; ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); @@ -385,7 +396,27 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p valid = dvalid || rvalid; r_missing_templates = !valid; - // Validate the rest of the configuration. + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} + +bool EditorExportPlatformJavaScript::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { +#ifndef DEV_ENABLED + // We don't provide export templates for the HTML5 platform currently as there + // is no suitable renderer to use with them. So we forbid exporting and tell + // users why. This is skipped in DEV_ENABLED so that contributors can still test + // the pipeline once we start having WebGL or WebGPU support. + r_error = "The HTML5 platform is currently not supported in Godot 4.0, as there is no suitable renderer for it.\n"; + return false; +#endif + + String err; + bool valid = true; + + // Validate the project configuration. if (p_preset->get("vram_texture_compression/for_mobile")) { String etc_error = test_etc2(); @@ -434,30 +465,30 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } if (!template_path.is_empty() && !FileAccess::exists(template_path)) { - EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Template file not found: \"%s\"."), template_path)); return ERR_FILE_NOT_FOUND; } // Export pck and shared objects Vector<SharedObject> shared_objects; String pck_path = base_path + ".pck"; - Error error = save_pack(p_preset, pck_path, &shared_objects); + Error error = save_pack(p_preset, p_debug, pck_path, &shared_objects); if (error != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), pck_path)); return error; } - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - for (int i = 0; i < shared_objects.size(); i++) { - String dst = 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; + + { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < shared_objects.size(); i++) { + String dst = base_dir.plus_file(shared_objects[i].path.get_file()); + error = da->copy(shared_objects[i].path, dst); + if (error != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), shared_objects[i].path.get_file())); + return error; + } } } - memdelete(da); - da = nullptr; // Extract templates. error = _extract_template(template_path, base_dir, base_name, pwa); @@ -467,32 +498,25 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese // Parse generated file sizes (pck and wasm, to help show a meaningful loading bar). Dictionary file_sizes; - FileAccess *f = nullptr; - f = FileAccess::open(pck_path, FileAccess::READ); - if (f) { + Ref<FileAccess> f = FileAccess::open(pck_path, FileAccess::READ); + if (f.is_valid()) { file_sizes[pck_path.get_file()] = (uint64_t)f->get_length(); - memdelete(f); - f = nullptr; } f = FileAccess::open(base_path + ".wasm", FileAccess::READ); - if (f) { + if (f.is_valid()) { file_sizes[base_name + ".wasm"] = (uint64_t)f->get_length(); - memdelete(f); - f = nullptr; } // Read the HTML shell file (custom or from template). const String html_path = custom_html.is_empty() ? base_path + ".html" : custom_html; Vector<uint8_t> html; f = FileAccess::open(html_path, FileAccess::READ); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path); + if (f.is_null()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not read HTML shell: \"%s\"."), html_path)); return ERR_FILE_CANT_READ; } html.resize(f->get_length()); f->get_buffer(html.ptrw(), html.size()); - memdelete(f); - f = nullptr; // Generate HTML file with replaced strings. _fix_html(html, p_preset, base_name, p_debug, p_flags, shared_objects, file_sizes); @@ -506,7 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese Ref<Image> splash = _get_project_splash(); const String splash_png_path = base_path + ".png"; if (splash->save_png(splash_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + splash_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), splash_png_path)); return ERR_FILE_CANT_WRITE; } @@ -516,13 +540,13 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese Ref<Image> favicon = _get_project_icon(); const String favicon_png_path = base_path + ".icon.png"; if (favicon->save_png(favicon_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + favicon_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), favicon_png_path)); return ERR_FILE_CANT_WRITE; } favicon->resize(180, 180); const String apple_icon_png_path = base_path + ".apple-touch-icon.png"; if (favicon->save_png(apple_icon_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + apple_icon_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), apple_icon_png_path)); return ERR_FILE_CANT_WRITE; } } @@ -578,14 +602,15 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese } const String dest = EditorPaths::get_singleton()->get_cache_dir().plus_file("web"); - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da->dir_exists(dest)) { Error err = da->make_dir_recursive(dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not create HTTP server directory:") + "\n" + dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Could not create HTTP server directory: %s."), dest)); return err; } } + const String basepath = dest.plus_file("tmp_js_export"); Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags); if (err != OK) { @@ -628,7 +653,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese err = server->listen(bind_port, bind_ip, use_ssl, ssl_key, ssl_cert); } if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Error starting HTTP server:") + "\n" + itos(err)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err)); return err; } @@ -643,9 +668,9 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const { } void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { - EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data; + EditorExportPlatformJavaScript *ej = static_cast<EditorExportPlatformJavaScript *>(data); while (!ej->server_quit) { - OS::get_singleton()->delay_usec(1000); + OS::get_singleton()->delay_usec(6900); { MutexLock lock(ej->server_lock); ej->server->poll(); @@ -657,13 +682,8 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { server.instantiate(); server_thread.start(_server_thread_poll, this); - Ref<Image> img = memnew(Image(_javascript_logo)); - logo.instantiate(); - logo->create_from_image(img); - - img = Ref<Image>(memnew(Image(_javascript_run_icon))); - run_icon.instantiate(); - run_icon->create_from_image(img); + logo = ImageTexture::create_from_image(memnew(Image(_javascript_logo))); + run_icon = ImageTexture::create_from_image(memnew(Image(_javascript_run_icon))); Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); if (theme.is_valid()) { diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index 8d4307548c..16bab02d54 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -36,15 +36,14 @@ #include "core/io/stream_peer_ssl.h" #include "core/io/tcp_server.h" #include "core/io/zip_io.h" -#include "editor/editor_export.h" +#include "editor/editor_node.h" +#include "editor/export/editor_export_platform.h" #include "main/splash.gen.h" #include "platform/javascript/logo.gen.h" #include "platform/javascript/run_icon.gen.h" #include "export_server.h" -class EditorNode; - class EditorExportPlatformJavaScript : public EditorExportPlatform { GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform); @@ -62,19 +61,16 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { EXPORT_MODE_NORMAL = 0, EXPORT_MODE_THREADS = 1, EXPORT_MODE_GDNATIVE = 2, + EXPORT_MODE_THREADS_GDNATIVE = 3, }; 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_mode & EXPORT_MODE_GDNATIVE) { + name += "_gdnative"; + } + if (p_mode & EXPORT_MODE_THREADS) { + name += "_threads"; } if (p_debug) { name += "_debug.zip"; @@ -105,7 +101,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { } Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); - void _replace_strings(Map<String, String> p_replaces, Vector<uint8_t> &r_template); + void _replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template); 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, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); @@ -114,7 +110,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { static void _server_thread_poll(void *data); public: - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override; + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; virtual void get_export_options(List<ExportOption> *r_options) override; @@ -122,7 +118,8 @@ public: virtual String get_os_name() const override; virtual Ref<Texture2D> get_logo() const override; - virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; + virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; @@ -134,12 +131,12 @@ public: virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; - virtual void get_platform_features(List<String> *r_features) override { + virtual void get_platform_features(List<String> *r_features) const override { r_features->push_back("web"); r_features->push_back(get_os_name().to_lower()); } - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override { + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) override { } String get_debug_protocol() const override { return "ws://"; } @@ -148,4 +145,4 @@ public: ~EditorExportPlatformJavaScript(); }; -#endif +#endif // JAVASCRIPT_EXPORT_PLUGIN_H diff --git a/platform/javascript/export/export_server.h b/platform/javascript/export/export_server.h index 1380f34ad7..ddbe3cca30 100644 --- a/platform/javascript/export/export_server.h +++ b/platform/javascript/export/export_server.h @@ -35,13 +35,12 @@ #include "core/io/stream_peer_ssl.h" #include "core/io/tcp_server.h" #include "core/io/zip_io.h" -#include "editor/editor_export.h" -#include "editor/editor_node.h" +#include "editor/editor_paths.h" class EditorHTTPServer : public RefCounted { private: Ref<TCPServer> server; - Map<String, String> mimes; + HashMap<String, String> mimes; Ref<StreamPeerTCP> tcp; Ref<StreamPeerSSL> ssl; Ref<StreamPeer> peer; @@ -153,8 +152,8 @@ public: } const String ctype = mimes[req_ext]; - FileAccess *f = FileAccess::open(filepath, FileAccess::READ); - ERR_FAIL_COND(!f); + Ref<FileAccess> f = FileAccess::open(filepath, FileAccess::READ); + ERR_FAIL_COND(f.is_null()); String s = "HTTP/1.1 200 OK\r\n"; s += "Connection: Close\r\n"; s += "Content-Type: " + ctype + "\r\n"; @@ -166,7 +165,6 @@ public: CharString cs = s.utf8(); Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); if (err != OK) { - memdelete(f); ERR_FAIL(); } @@ -178,11 +176,9 @@ public: } err = peer->put_data(bytes, read); if (err != OK) { - memdelete(f); ERR_FAIL(); } } - memdelete(f); } void poll() { @@ -251,4 +247,4 @@ public: } }; -#endif +#endif // JAVASCRIPT_EXPORT_SERVER_H diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index 012f8daeb7..3855b7301e 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -63,4 +63,4 @@ extern void godot_audio_script_start(float *p_in_buf, int p_in_size, float *p_ou } #endif -#endif /* GODOT_AUDIO_H */ +#endif // GODOT_AUDIO_H diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 2cb5c3025c..a323f2d157 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -58,6 +58,7 @@ extern void godot_js_input_mouse_move_cb(void (*p_callback)(double p_x, double p extern void godot_js_input_mouse_wheel_cb(int (*p_callback)(double p_delta_x, double p_delta_y)); extern void godot_js_input_touch_cb(void (*p_callback)(int p_type, int p_count), uint32_t *r_identifiers, double *r_coords); extern void godot_js_input_key_cb(void (*p_callback)(int p_type, int p_repeat, int p_modifiers), char r_code[32], char r_key[32]); +extern void godot_js_input_vibrate_handheld(int p_duration_ms); // Input gamepad extern void godot_js_input_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid)); @@ -67,6 +68,15 @@ extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_ extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text)); extern void godot_js_input_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); +// TTS +extern int godot_js_tts_is_speaking(); +extern int godot_js_tts_is_paused(); +extern int godot_js_tts_get_voices(void (*p_callback)(int p_size, const char **p_voices)); +extern void godot_js_tts_speak(const char *p_text, const char *p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, void (*p_callback)(int p_event, int p_id, int p_pos)); +extern void godot_js_tts_pause(); +extern void godot_js_tts_resume(); +extern void godot_js_tts_stop(); + // Display extern int godot_js_display_screen_dpi_get(); extern double godot_js_display_pixel_ratio_get(); @@ -109,12 +119,13 @@ extern void godot_js_display_notification_cb(void (*p_callback)(int p_notificati // Display Virtual Keyboard extern int godot_js_display_vk_available(); +extern int godot_js_display_tts_available(); extern void godot_js_display_vk_cb(void (*p_input)(const char *p_text, int p_cursor)); -extern void godot_js_display_vk_show(const char *p_text, int p_multiline, int p_start, int p_end); +extern void godot_js_display_vk_show(const char *p_text, int p_type, int p_start, int p_end); extern void godot_js_display_vk_hide(); #ifdef __cplusplus } #endif -#endif /* GODOT_JS_H */ +#endif // GODOT_JS_H diff --git a/platform/javascript/godot_webgl2.h b/platform/javascript/godot_webgl2.h new file mode 100644 index 0000000000..968b70f84b --- /dev/null +++ b/platform/javascript/godot_webgl2.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* godot_webgl2.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifndef GODOT_WEBGL2_H +#define GODOT_WEBGL2_H + +#include "GLES3/gl3.h" +#include "webgl/webgl2.h" + +#endif // GODOT_WEBGL2_H diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index c946302862..32bdfed4c7 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -76,7 +76,7 @@ void HTTPClientJavaScript::set_connection(const Ref<StreamPeer> &p_connection) { } Ref<StreamPeer> HTTPClientJavaScript::get_connection() const { - ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform."); + ERR_FAIL_V_MSG(Ref<RefCounted>(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform."); } Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) { diff --git a/platform/javascript/http_client_javascript.h b/platform/javascript/http_client_javascript.h index 096aa6a153..fcd225ffc9 100644 --- a/platform/javascript/http_client_javascript.h +++ b/platform/javascript/http_client_javascript.h @@ -105,4 +105,5 @@ public: HTTPClientJavaScript(); ~HTTPClientJavaScript(); }; + #endif // HTTP_CLIENT_JAVASCRIPT_H diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 5c00476a72..307a80feea 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -28,6 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "core/config/engine.h" #include "core/io/resource_loader.h" #include "main/main.h" #include "platform/javascript/display_server_javascript.h" @@ -94,7 +95,7 @@ extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) { Main::start(); os->get_main_loop()->initialize(); #ifdef TOOLS_ENABLED - if (Main::is_project_manager() && FileAccess::exists("/tmp/preload.zip")) { + if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) { PackedStringArray ps; ps.push_back("/tmp/preload.zip"); os->get_main_loop()->emit_signal(SNAME("files_dropped"), ps, -1); diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp index 77858bff01..204e92b82b 100644 --- a/platform/javascript/javascript_singleton.cpp +++ b/platform/javascript/javascript_singleton.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "api/javascript_singleton.h" + #include "emscripten.h" #include "os_javascript.h" @@ -80,7 +81,7 @@ protected: public: Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override; - Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; + Variant callp(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; JavaScriptObjectImpl() {} JavaScriptObjectImpl(int p_id) { _js_id = p_id; } ~JavaScriptObjectImpl() { @@ -206,7 +207,7 @@ int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_w break; case Variant::INT: { const int64_t tmp = v->operator int64_t(); - if (tmp >= 1 << 31) { + if (tmp >= 1LL << 31) { r_val->r = (double)tmp; return Variant::FLOAT; } @@ -230,7 +231,7 @@ int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_w return type; } -Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { +Variant JavaScriptObjectImpl::callp(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { godot_js_wrapper_ex exchange; const String method = p_method; void *lock = nullptr; @@ -258,7 +259,7 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { const Variant *argv[1] = { &arg }; Callable::CallError err; Variant ret; - obj->_callable.call(argv, 1, ret, err); + obj->_callable.callp(argv, 1, ret, err); // Set return value godot_js_wrapper_ex exchange; diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js index 2e5e1ed0d1..9c4b6c2012 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -334,6 +334,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- locale = navigator.languages ? navigator.languages[0] : navigator.language; locale = locale.split('.')[0]; } + locale = locale.replace('-', '_'); const onExit = this.onExit; // Godot configuration. diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index 2bdf4e56ad..c7729a8c5b 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -73,7 +73,7 @@ const GodotDisplayVK = { GodotDisplayVK.textarea = create('textarea'); GodotDisplayVK.updateSize(); }, - show: function (text, multiline, start, end) { + show: function (text, type, start, end) { if (!GodotDisplayVK.textinput || !GodotDisplayVK.textarea) { return; } @@ -81,7 +81,46 @@ const GodotDisplayVK = { GodotDisplayVK.hide(); } GodotDisplayVK.updateSize(); - const elem = multiline ? GodotDisplayVK.textarea : GodotDisplayVK.textinput; + + let elem = GodotDisplayVK.textinput; + switch (type) { + case 0: // KEYBOARD_TYPE_DEFAULT + elem.type = 'text'; + elem.inputmode = ''; + break; + case 1: // KEYBOARD_TYPE_MULTILINE + elem = GodotDisplayVK.textarea; + break; + case 2: // KEYBOARD_TYPE_NUMBER + elem.type = 'text'; + elem.inputmode = 'numeric'; + break; + case 3: // KEYBOARD_TYPE_NUMBER_DECIMAL + elem.type = 'text'; + elem.inputmode = 'decimal'; + break; + case 4: // KEYBOARD_TYPE_PHONE + elem.type = 'tel'; + elem.inputmode = ''; + break; + case 5: // KEYBOARD_TYPE_EMAIL_ADDRESS + elem.type = 'email'; + elem.inputmode = ''; + break; + case 6: // KEYBOARD_TYPE_PASSWORD + elem.type = 'password'; + elem.inputmode = ''; + break; + case 7: // KEYBOARD_TYPE_URL + elem.type = 'url'; + elem.inputmode = ''; + break; + default: + elem.type = 'text'; + elem.inputmode = ''; + break; + } + elem.readonly = false; elem.disabled = false; elem.value = text; @@ -330,6 +369,91 @@ const GodotDisplay = { return 0; }, + godot_js_tts_is_speaking__sig: 'i', + godot_js_tts_is_speaking: function () { + return window.speechSynthesis.speaking; + }, + + godot_js_tts_is_paused__sig: 'i', + godot_js_tts_is_paused: function () { + return window.speechSynthesis.paused; + }, + + godot_js_tts_get_voices__sig: 'vi', + godot_js_tts_get_voices: function (p_callback) { + const func = GodotRuntime.get_func(p_callback); + try { + const arr = []; + const voices = window.speechSynthesis.getVoices(); + for (let i = 0; i < voices.length; i++) { + arr.push(`${voices[i].lang};${voices[i].name}`); + } + const c_ptr = GodotRuntime.allocStringArray(arr); + func(arr.length, c_ptr); + GodotRuntime.freeStringArray(c_ptr, arr.length); + } catch (e) { + // Fail graciously. + } + }, + + godot_js_tts_speak__sig: 'viiiffii', + godot_js_tts_speak: function (p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_callback) { + const func = GodotRuntime.get_func(p_callback); + + function listener_end(evt) { + evt.currentTarget.cb(1 /*TTS_UTTERANCE_ENDED*/, evt.currentTarget.id, 0); + } + + function listener_start(evt) { + evt.currentTarget.cb(0 /*TTS_UTTERANCE_STARTED*/, evt.currentTarget.id, 0); + } + + function listener_error(evt) { + evt.currentTarget.cb(2 /*TTS_UTTERANCE_CANCELED*/, evt.currentTarget.id, 0); + } + + function listener_bound(evt) { + evt.currentTarget.cb(3 /*TTS_UTTERANCE_BOUNDARY*/, evt.currentTarget.id, evt.charIndex); + } + + const utterance = new SpeechSynthesisUtterance(GodotRuntime.parseString(p_text)); + utterance.rate = p_rate; + utterance.pitch = p_pitch; + utterance.volume = p_volume / 100.0; + utterance.addEventListener('end', listener_end); + utterance.addEventListener('start', listener_start); + utterance.addEventListener('error', listener_error); + utterance.addEventListener('boundary', listener_bound); + utterance.id = p_utterance_id; + utterance.cb = func; + const voice = GodotRuntime.parseString(p_voice); + const voices = window.speechSynthesis.getVoices(); + for (let i = 0; i < voices.length; i++) { + if (voices[i].name === voice) { + utterance.voice = voices[i]; + break; + } + } + window.speechSynthesis.resume(); + window.speechSynthesis.speak(utterance); + }, + + godot_js_tts_pause__sig: 'v', + godot_js_tts_pause: function () { + window.speechSynthesis.pause(); + }, + + godot_js_tts_resume__sig: 'v', + godot_js_tts_resume: function () { + window.speechSynthesis.resume(); + }, + + godot_js_tts_stop__sig: 'v', + godot_js_tts_stop: function () { + window.speechSynthesis.cancel(); + window.speechSynthesis.resume(); + }, + godot_js_display_alert__sig: 'vi', godot_js_display_alert: function (p_text) { window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert @@ -377,6 +501,7 @@ const GodotDisplay = { GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32'); }, + godot_js_display_window_size_get__sig: 'vii', godot_js_display_window_size_get: function (p_width, p_height) { GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32'); GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32'); @@ -608,11 +733,11 @@ const GodotDisplay = { * Virtual Keyboard */ godot_js_display_vk_show__sig: 'viiii', - godot_js_display_vk_show: function (p_text, p_multiline, p_start, p_end) { + godot_js_display_vk_show: function (p_text, p_type, p_start, p_end) { const text = GodotRuntime.parseString(p_text); const start = p_start > 0 ? p_start : 0; const end = p_end > 0 ? p_end : start; - GodotDisplayVK.show(text, p_multiline, start, end); + GodotDisplayVK.show(text, p_type, start, end); }, godot_js_display_vk_hide__sig: 'v', @@ -625,6 +750,11 @@ const GodotDisplay = { return GodotDisplayVK.available(); }, + godot_js_display_tts_available__sig: 'i', + godot_js_display_tts_available: function () { + return 'speechSynthesis' in window; + }, + godot_js_display_vk_cb__sig: 'vi', godot_js_display_vk_cb: function (p_input_cb) { const input_cb = GodotRuntime.get_func(p_input_cb); diff --git a/platform/javascript/js/libs/library_godot_fetch.js b/platform/javascript/js/libs/library_godot_fetch.js index 007e7b70f5..285e50a035 100644 --- a/platform/javascript/js/libs/library_godot_fetch.js +++ b/platform/javascript/js/libs/library_godot_fetch.js @@ -89,7 +89,6 @@ const GodotFetch = { method: method, headers: headers, body: body, - credentials: 'include', }; obj.request = fetch(url, init); obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); diff --git a/platform/javascript/js/libs/library_godot_input.js b/platform/javascript/js/libs/library_godot_input.js index 1e64c260f8..51571d64a2 100644 --- a/platform/javascript/js/libs/library_godot_input.js +++ b/platform/javascript/js/libs/library_godot_input.js @@ -534,6 +534,15 @@ const GodotInput = { GodotRuntime.free(ptr); }, false); }, + + godot_js_input_vibrate_handheld__sig: 'vi', + godot_js_input_vibrate_handheld: function (p_duration_ms) { + if (typeof navigator.vibrate !== 'function') { + GodotRuntime.print('This browser does not support vibration.'); + } else { + navigator.vibrate(p_duration_ms); + } + }, }; autoAddDeps(GodotInput, '$GodotInput'); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 12d06a8d51..377eec3234 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -305,7 +305,9 @@ const GodotOS = { godot_js_os_hw_concurrency_get__sig: 'i', godot_js_os_hw_concurrency_get: function () { - return navigator.hardwareConcurrency || 1; + // TODO Godot core needs fixing to avoid spawning too many threads (> 24). + const concurrency = navigator.hardwareConcurrency || 1; + return concurrency < 2 ? concurrency : 2; }, godot_js_os_download_buffer__sig: 'viiii', diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index de5ca44f9d..dc81b8b4b6 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -131,6 +131,10 @@ int OS_JavaScript::get_process_id() const { ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform."); } +bool OS_JavaScript::is_process_running(const ProcessID &p_pid) const { + return false; +} + int OS_JavaScript::get_processor_count() const { return godot_js_os_hw_concurrency_get(); } @@ -173,9 +177,13 @@ String OS_JavaScript::get_name() const { return "HTML5"; } +void OS_JavaScript::vibrate_handheld(int p_duration_ms) { + godot_js_input_vibrate_handheld(p_duration_ms); +} + String OS_JavaScript::get_user_data_dir() const { return "/userfs"; -}; +} String OS_JavaScript::get_cache_path() const { return "/home/web_user/.cache"; @@ -221,10 +229,15 @@ 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) { +Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_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()); + + if (r_resolved_path != nullptr) { + *r_resolved_path = path; + } + return OK; } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 9e272f9aa1..35e13c94fc 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -79,6 +79,7 @@ public: Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; Error kill(const ProcessID &p_pid) override; int get_process_id() const override; + bool is_process_running(const ProcessID &p_pid) const override; int get_processor_count() const override; int get_default_thread_pool_size() const override { return 1; } @@ -89,6 +90,8 @@ public: // Implemented in javascript_main.cpp loop callback instead. void add_frame_delay(bool p_can_draw) override {} + void vibrate_handheld(int p_duration_ms) override; + String get_cache_path() const override; String get_config_path() const override; String get_data_path() const override; @@ -99,11 +102,11 @@ public: void alert(const String &p_alert, const String &p_title = "ALERT!") override; - Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override; + Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; void resume_audio(); OS_JavaScript(); }; -#endif +#endif // OS_JAVASCRIPT_H diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json index 35f864f01a..f8c67b206f 100644 --- a/platform/javascript/package-lock.json +++ b/platform/javascript/package-lock.json @@ -109,6 +109,28 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, "node_modules/@zeit/schemas": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", @@ -658,10 +680,13 @@ } }, "node_modules/entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/error-ex": { "version": "1.3.2", @@ -1637,34 +1662,35 @@ } }, "node_modules/js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, "dependencies": { - "xmlcreate": "^2.0.3" + "xmlcreate": "^2.0.4" } }, "node_modules/jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "version": "3.6.10", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", + "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", "dev": true, "dependencies": { "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", + "js2xmlparser": "^4.0.2", + "klaw": "^4.0.1", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.13.1" + "underscore": "~1.13.2" }, "bin": { "jsdoc": "jsdoc.js" @@ -1713,12 +1739,12 @@ } }, "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.0.1.tgz", + "integrity": "sha512-pgsE40/SvC7st04AHiISNewaIMUbY5V/K8b21ekiPiFoYs/EYSdsGa+FJArB1d441uq4Q8zZyIxvAzkGNlBdRw==", "dev": true, - "dependencies": { - "graceful-fs": "^4.1.9" + "engines": { + "node": ">=14.14.0" } }, "node_modules/levn": { @@ -1735,9 +1761,9 @@ } }, "node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "dependencies": { "uc.micro": "^1.0.1" @@ -1808,14 +1834,14 @@ } }, "node_modules/markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, @@ -1824,24 +1850,31 @@ } }, "node_modules/markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz", + "integrity": "sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img==", "dev": true, "peerDependencies": { + "@types/markdown-it": "*", "markdown-it": "*" } }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/marked": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.7.tgz", - "integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", + "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", "dev": true, "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">= 8.16.2" + "node": ">= 12" } }, "node_modules/mdurl": { @@ -1884,9 +1917,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/mkdirp": { @@ -2834,9 +2867,9 @@ } }, "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, "node_modules/update-check": { @@ -2992,9 +3025,9 @@ "dev": true }, "node_modules/xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, "node_modules/yallist": { @@ -3079,6 +3112,28 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, "@zeit/schemas": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", @@ -3493,9 +3548,9 @@ } }, "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true }, "error-ex": { @@ -4239,34 +4294,35 @@ } }, "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, "requires": { - "xmlcreate": "^2.0.3" + "xmlcreate": "^2.0.4" } }, "jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", + "version": "3.6.10", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", + "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", "dev": true, "requires": { "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", + "js2xmlparser": "^4.0.2", + "klaw": "^4.0.1", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.13.1" + "underscore": "~1.13.2" }, "dependencies": { "escape-string-regexp": { @@ -4305,13 +4361,10 @@ } }, "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.0.1.tgz", + "integrity": "sha512-pgsE40/SvC7st04AHiISNewaIMUbY5V/K8b21ekiPiFoYs/EYSdsGa+FJArB1d441uq4Q8zZyIxvAzkGNlBdRw==", + "dev": true }, "levn": { "version": "0.4.1", @@ -4324,9 +4377,9 @@ } }, "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "requires": { "uc.micro": "^1.0.1" @@ -4388,29 +4441,37 @@ } }, "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, "markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz", + "integrity": "sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img==", "dev": true, "requires": {} }, "marked": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.7.tgz", - "integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", + "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", "dev": true }, "mdurl": { @@ -4444,9 +4505,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { @@ -5188,9 +5249,9 @@ } }, "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz", + "integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA==", "dev": true }, "update-check": { @@ -5315,9 +5376,9 @@ "dev": true }, "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, "yallist": { diff --git a/platform/javascript/package.json b/platform/javascript/package.json index 2ff1544837..8c38bc89e8 100644 --- a/platform/javascript/package.json +++ b/platform/javascript/package.json @@ -15,7 +15,7 @@ "format:libs": "npm run lint:libs -- --fix", "format:modules": "npm run lint:modules -- --fix", "format:tools": "npm run lint:tools -- --fix", - "serve": "serve" + "serve": "serve" }, "author": "Godot Engine contributors", "license": "MIT", diff --git a/platform/javascript/platform_config.h b/platform/javascript/platform_config.h index ba1b0d459e..1970fe0fa0 100644 --- a/platform/javascript/platform_config.h +++ b/platform/javascript/platform_config.h @@ -29,3 +29,5 @@ /*************************************************************************/ #include <alloca.h> + +#define OPENGL_INCLUDE_H "platform/javascript/godot_webgl2.h" |