diff options
Diffstat (limited to 'platform/javascript')
-rw-r--r-- | platform/javascript/README.md | 15 | ||||
-rw-r--r-- | platform/javascript/api/javascript_tools_editor_plugin.cpp | 32 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.cpp | 2 | ||||
-rw-r--r-- | platform/javascript/display_server_javascript.cpp | 21 | ||||
-rw-r--r-- | platform/javascript/export/export_plugin.h | 2 | ||||
-rw-r--r-- | platform/javascript/godot_audio.h | 2 | ||||
-rw-r--r-- | platform/javascript/js/libs/audio.worklet.js | 14 | ||||
-rw-r--r-- | platform/javascript/js/libs/library_godot_audio.js | 17 | ||||
-rw-r--r-- | platform/javascript/os_javascript.cpp | 4 |
9 files changed, 85 insertions, 24 deletions
diff --git a/platform/javascript/README.md b/platform/javascript/README.md new file mode 100644 index 0000000000..f181bea9e0 --- /dev/null +++ b/platform/javascript/README.md @@ -0,0 +1,15 @@ +# HTML5 platform port + +This folder contains the C++ and JavaScript code for the HTML5/WebAssembly platform port, +compiled using [Emscripten](https://emscripten.org/). + +It also contains a ESLint linting setup (see [`package.json`](package.json)). + +See also [`misc/dist/html`](/misc/dist/html) folder for files used by this platform +such as the HTML5 shell. + +## Artwork license + +[`logo.png`](logo.png) and [`run_icon.png`](run_icon.png) are licensed under +[Creative Commons Attribution 3.0 Unported](https://www.w3.org/html/logo/faq.html#how-licenced) +per the HTML5 logo usage guidelines. diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 54f541f607..45a2cd595a 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/os/time.h" #include "editor/editor_node.h" #include <emscripten/emscripten.h> @@ -58,23 +59,40 @@ JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) { - WARN_PRINT("Project download is only available in Editor mode"); + ERR_PRINT("Downloading the project as a ZIP archive is only available in Editor mode."); return; } String resource_path = ProjectSettings::get_singleton()->get_resource_path(); FileAccess *src_f; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, nullptr, &io); - String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; + + // Name the downloded 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. + // In the project name, all invalid characters become an empty string so that a name + // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". + const String project_name = GLOBAL_GET("application/config/name"); + const String project_name_safe = project_name.to_lower().replace(" ", "_"); + const String datetime_safe = + Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); + const String output_path = String("/tmp").plus_file(output_name); + + zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); + 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("/tmp/project.zip", FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Unable to create zip file"); + 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(), "project.zip", "application/zip"); + 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) { @@ -108,7 +126,7 @@ 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) { - WARN_PRINT("Unable to open dir for zipping: " + p_path); + WARN_PRINT("Unable to open directory for zipping: " + p_path); return; } dir->list_dir_begin(); diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 478e848675..420cb2f2f7 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -108,7 +108,7 @@ Error AudioDriverJavaScript::init() { mix_rate = GLOBAL_GET("audio/driver/mix_rate"); int latency = GLOBAL_GET("audio/driver/output_latency"); - channel_count = godot_audio_init(mix_rate, latency, &_state_change_callback, &_latency_update_callback); + channel_count = godot_audio_init(&mix_rate, latency, &_state_change_callback, &_latency_update_callback); buffer_length = closest_power_of_2((latency * mix_rate / 1000)); #ifndef NO_THREADS node = memnew(WorkletNode); diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index fda18a5c19..b43614eb99 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -158,6 +158,10 @@ EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const Emscri return false; } Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return true; } @@ -165,6 +169,10 @@ EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const Emscr DisplayServerJavaScript *display = get_singleton(); display->deferred_key_event->set_unicode(p_event->charCode); Input::get_singleton()->parse_input_event(display->deferred_key_event); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return true; } @@ -172,6 +180,10 @@ EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const Emscript Ref<InputEventKey> ev = setup_key_event(p_event); ev->set_pressed(false); Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != (Key)0; } @@ -245,6 +257,10 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E ev->set_button_mask(mask); input->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + // Prevent multi-click text selection and wheel-click scrolling anchor. // Context menu is prevented through contextmenu event. return true; @@ -507,6 +523,10 @@ EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const Em Input::get_singleton()->parse_input_event(ev); } + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + // Resume audio context after input in case autoplay was denied. return true; } @@ -1019,6 +1039,7 @@ bool DisplayServerJavaScript::can_any_window_draw() const { } void DisplayServerJavaScript::process_events() { + Input::get_singleton()->flush_buffered_events(); if (godot_js_display_gamepad_sample() == OK) { process_joypads(); } diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index 736edfe3a8..bdd1235259 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -134,7 +134,7 @@ public: virtual void get_platform_features(List<String> *r_features) override { r_features->push_back("web"); - r_features->push_back(get_os_name()); + 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 { diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h index 54fc8fa3b5..de8f046bbd 100644 --- a/platform/javascript/godot_audio.h +++ b/platform/javascript/godot_audio.h @@ -38,7 +38,7 @@ extern "C" { #include "stddef.h" extern int godot_audio_is_available(); -extern int godot_audio_init(int p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float)); +extern int godot_audio_init(int *p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float)); extern void godot_audio_resume(); extern int godot_audio_capture_start(); diff --git a/platform/javascript/js/libs/audio.worklet.js b/platform/javascript/js/libs/audio.worklet.js index 866f845139..df475ba52d 100644 --- a/platform/javascript/js/libs/audio.worklet.js +++ b/platform/javascript/js/libs/audio.worklet.js @@ -66,17 +66,17 @@ class RingBuffer { const mw = this.buffer.length - this.wpos; if (mw >= to_write) { this.buffer.set(p_buffer, this.wpos); + this.wpos += to_write; + if (mw === to_write) { + this.wpos = 0; + } } else { - const high = p_buffer.subarray(0, to_write - mw); - const low = p_buffer.subarray(to_write - mw); + const high = p_buffer.subarray(0, mw); + const low = p_buffer.subarray(mw); this.buffer.set(high, this.wpos); this.buffer.set(low); + this.wpos = low.length; } - let diff = to_write; - if (this.wpos + diff >= this.buffer.length) { - diff -= this.buffer.length; - } - this.wpos += diff; Atomics.add(this.avail, 0, to_write); Atomics.notify(this.avail, 0); } diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index 45c3a3fe2e..c9dae1a7af 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -37,10 +37,14 @@ const GodotAudio = { interval: 0, init: function (mix_rate, latency, onstatechange, onlatencyupdate) { - const ctx = new (window.AudioContext || window.webkitAudioContext)({ - sampleRate: mix_rate, - // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance. - }); + const opts = {}; + // If mix_rate is 0, let the browser choose. + if (mix_rate) { + opts['sampleRate'] = mix_rate; + } + // Do not specify, leave 'interactive' for good performance. + // opts['latencyHint'] = latency / 1000; + const ctx = new (window.AudioContext || window.webkitAudioContext)(opts); GodotAudio.ctx = ctx; ctx.onstatechange = function () { let state = 0; @@ -159,7 +163,10 @@ const GodotAudio = { godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { const statechange = GodotRuntime.get_func(p_state_change); const latencyupdate = GodotRuntime.get_func(p_latency_update); - return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); + const mix_rate = GodotRuntime.getHeapValue(p_mix_rate, 'i32'); + const channels = GodotAudio.init(mix_rate, p_latency, statechange, latencyupdate); + GodotRuntime.setHeapValue(p_mix_rate, GodotAudio.ctx.sampleRate, 'i32'); + return channels; }, godot_audio_resume__sig: 'v', diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 76102d941b..95c5909d50 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -137,12 +137,12 @@ int OS_JavaScript::get_processor_count() const { } bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "HTML5" || p_feature == "web") { + if (p_feature == "html5" || p_feature == "web") { return true; } #ifdef JAVASCRIPT_EVAL_ENABLED - if (p_feature == "JavaScript") { + if (p_feature == "javascript") { return true; } #endif |