summaryrefslogtreecommitdiffstats
path: root/platform/web
diff options
context:
space:
mode:
Diffstat (limited to 'platform/web')
-rw-r--r--platform/web/.eslintrc.html.js2
-rw-r--r--platform/web/SCsub4
-rw-r--r--platform/web/audio_driver_web.cpp52
-rw-r--r--platform/web/audio_driver_web.h51
-rw-r--r--platform/web/detect.py45
-rw-r--r--platform/web/display_server_web.cpp3
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/doc_classes/EditorExportPlatformWeb.xml4
-rw-r--r--platform/web/dom_keys.inc21
-rw-r--r--platform/web/emscripten_helpers.py41
-rw-r--r--platform/web/export/editor_http_server.cpp255
-rw-r--r--platform/web/export/editor_http_server.h203
-rw-r--r--platform/web/export/export_plugin.cpp59
-rw-r--r--platform/web/export/export_plugin.h12
-rw-r--r--platform/web/js/engine/config.js2
-rw-r--r--platform/web/js/engine/features.js22
-rw-r--r--platform/web/js/libs/audio.worklet.js4
17 files changed, 510 insertions, 271 deletions
diff --git a/platform/web/.eslintrc.html.js b/platform/web/.eslintrc.html.js
index 5cb8de360a..8c9a3d83da 100644
--- a/platform/web/.eslintrc.html.js
+++ b/platform/web/.eslintrc.html.js
@@ -15,5 +15,7 @@ module.exports = {
"Godot": true,
"Engine": true,
"$GODOT_CONFIG": true,
+ "$GODOT_THREADS_ENABLED": true,
+ "___GODOT_THREADS_ENABLED___": true,
},
};
diff --git a/platform/web/SCsub b/platform/web/SCsub
index 1af0642554..3e0cc9ac4a 100644
--- a/platform/web/SCsub
+++ b/platform/web/SCsub
@@ -13,7 +13,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
except Exception:
print("GODOT_WEB_TEST_PORT must be a valid integer")
sys.exit(255)
- serve(env.Dir("#bin/.web_zip").abspath, port, "run" in COMMAND_LINE_TARGETS)
+ serve(env.Dir(env.GetTemplateZipPath()).abspath, port, "run" in COMMAND_LINE_TARGETS)
sys.exit(0)
web_files = [
@@ -95,7 +95,7 @@ engine = [
"js/engine/engine.js",
]
externs = [env.File("#platform/web/js/engine/engine.externs.js")]
-js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs)
+js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs, env["threads"])
env.Depends(js_engine, externs)
wrap_list = [
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index 1298d28ebf..ec3c22bf7c 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -30,6 +30,8 @@
#include "audio_driver_web.h"
+#include "godot_audio.h"
+
#include "core/config/project_settings.h"
#include <emscripten.h>
@@ -184,6 +186,8 @@ Error AudioDriverWeb::input_stop() {
return OK;
}
+#ifdef THREADS_ENABLED
+
/// AudioWorkletNode implementation (threads)
void AudioDriverWorklet::_audio_thread_func(void *p_data) {
AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data);
@@ -245,3 +249,51 @@ void AudioDriverWorklet::finish_driver() {
quit = true; // Ask thread to quit.
thread.wait_to_finish();
}
+
+#else // No threads.
+
+/// AudioWorkletNode implementation (no threads)
+AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr;
+
+Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) {
+ if (!godot_audio_has_worklet()) {
+ return ERR_UNAVAILABLE;
+ }
+ return (Error)godot_audio_worklet_create(p_channels);
+}
+
+void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ _audio_driver_process();
+ godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback);
+}
+
+void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) {
+ AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
+ driver->_audio_driver_process(p_pos, p_samples);
+}
+
+void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) {
+ AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton();
+ driver->_audio_driver_capture(p_pos, p_samples);
+}
+
+/// ScriptProcessorNode implementation
+AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr;
+
+void AudioDriverScriptProcessor::_process_callback() {
+ AudioDriverScriptProcessor::get_singleton()->_audio_driver_capture();
+ AudioDriverScriptProcessor::get_singleton()->_audio_driver_process();
+}
+
+Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) {
+ if (!godot_audio_has_script_processor()) {
+ return ERR_UNAVAILABLE;
+ }
+ return (Error)godot_audio_script_create(&p_buffer_samples, p_channels);
+}
+
+void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
+}
+
+#endif // THREADS_ENABLED
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index 12a61746c3..df88d0a94c 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -90,6 +90,7 @@ public:
AudioDriverWeb() {}
};
+#ifdef THREADS_ENABLED
class AudioDriverWorklet : public AudioDriverWeb {
private:
enum {
@@ -120,4 +121,54 @@ public:
virtual void unlock() override;
};
+#else
+
+class AudioDriverWorklet : public AudioDriverWeb {
+private:
+ static void _process_callback(int p_pos, int p_samples);
+ static void _capture_callback(int p_pos, int p_samples);
+
+ static AudioDriverWorklet *singleton;
+
+protected:
+ virtual Error create(int &p_buffer_size, int p_output_channels) override;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+
+public:
+ virtual const char *get_name() const override {
+ return "AudioWorklet";
+ }
+
+ virtual void lock() override {}
+ virtual void unlock() override {}
+
+ static AudioDriverWorklet *get_singleton() { return singleton; }
+
+ AudioDriverWorklet() { singleton = this; }
+};
+
+class AudioDriverScriptProcessor : public AudioDriverWeb {
+private:
+ static void _process_callback();
+
+ static AudioDriverScriptProcessor *singleton;
+
+protected:
+ virtual Error create(int &p_buffer_size, int p_output_channels) override;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ virtual void finish_driver() override;
+
+public:
+ virtual const char *get_name() const override { return "ScriptProcessor"; }
+
+ virtual void lock() override {}
+ virtual void unlock() override {}
+
+ static AudioDriverScriptProcessor *get_singleton() { return singleton; }
+
+ AudioDriverScriptProcessor() { singleton = this; }
+};
+
+#endif // THREADS_ENABLED
+
#endif // AUDIO_DRIVER_WEB_H
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 579eaaff03..bbe1634dfa 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -8,6 +8,7 @@ from emscripten_helpers import (
add_js_pre,
add_js_externs,
create_template_zip,
+ get_template_zip_path,
)
from methods import get_compiler_version
from SCons.Util import WhereIs
@@ -30,6 +31,9 @@ def get_opts():
return [
("initial_memory", "Initial WASM memory (in MiB)", 32),
+ # Matches default values from before Emscripten 3.1.27. New defaults are too low for Godot.
+ ("stack_size", "WASM stack size (in KiB)", 5120),
+ ("default_pthread_stack_size", "WASM pthread default stack size (in KiB)", 2048),
BoolVariable("use_assertions", "Use Emscripten runtime assertions", False),
BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False),
BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False),
@@ -158,11 +162,14 @@ def configure(env: "Environment"):
# Add method that joins/compiles our Engine files.
env.AddMethod(create_engine_file, "CreateEngineFile")
+ # Add method for getting the final zip path
+ env.AddMethod(get_template_zip_path, "GetTemplateZipPath")
+
# Add method for creating the final zip file
env.AddMethod(create_template_zip, "CreateTemplateZip")
# Closure compiler extern and support for ecmascript specs (const, let, etc).
- env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2020"
+ env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2021"
env["CC"] = "emcc"
env["CXX"] = "em++"
@@ -193,37 +200,47 @@ def configure(env: "Environment"):
env.Prepend(CPPPATH=["#platform/web"])
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])
- if cc_semver >= (3, 1, 25):
- env.Append(LINKFLAGS=["-s", "STACK_SIZE=5MB"])
- else:
- env.Append(LINKFLAGS=["-s", "TOTAL_STACK=5MB"])
-
if env["opengl3"]:
env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
# Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
+ # Breaking change since emscripten 3.1.51
+ # https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md#3151---121323
+ if cc_semver >= (3, 1, 51):
+ # Enables the use of *glGetProcAddress()
+ env.Append(LINKFLAGS=["-s", "GL_ENABLE_GET_PROC_ADDRESS=1"])
if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
- # Thread support (via SharedArrayBuffer).
- env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
- env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
- env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
- env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=2MB"])
- env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
- env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK"
+ env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])])
+
+ if env["threads"]:
+ # Thread support (via SharedArrayBuffer).
+ env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
+ env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
+ env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]])
+ env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
+ env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ elif env["proxy_to_pthread"]:
+ print('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.')
+ env["proxy_to_pthread"] = False
if env["lto"] != "none":
# Workaround https://github.com/emscripten-core/emscripten/issues/19781.
if cc_semver >= (3, 1, 42) and cc_semver < (3, 1, 46):
env.Append(LINKFLAGS=["-Wl,-u,scalbnf"])
+ # Workaround https://github.com/emscripten-core/emscripten/issues/16836.
+ if cc_semver >= (3, 1, 47):
+ env.Append(LINKFLAGS=["-Wl,-u,_emscripten_run_callback_on_thread"])
if env["dlink_enabled"]:
if env["proxy_to_pthread"]:
- print("GDExtension support requires proxy_to_pthread=no, disabling")
+ print("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.")
env["proxy_to_pthread"] = False
if cc_semver < (3, 1, 14):
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index b4a190d47e..aacbe4879f 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
+ KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data());
DisplayServerWeb::KeyEvent ke;
@@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
ke.physical_keycode = scancode;
ke.key_label = fix_key_label(c, keycode);
ke.unicode = fix_unicode(c);
+ ke.location = location;
ke.mod = p_modifiers;
if (ds->key_event_pos >= ds->key_event_buffer.size()) {
@@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() {
ev->set_physical_keycode(ke.physical_keycode);
ev->set_key_label(ke.key_label);
ev->set_unicode(ke.unicode);
+ ev->set_location(ke.location);
if (ke.raw) {
dom2godot_mod(ev, ke.mod, ke.keycode);
}
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 140aef952b..682d10704f 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -95,6 +95,7 @@ private:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
int mod = 0;
};
diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml
index c4c4fd870b..f07f265b0d 100644
--- a/platform/web/doc_classes/EditorExportPlatformWeb.xml
+++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml
@@ -47,6 +47,10 @@
</member>
<member name="variant/extensions_support" type="bool" setter="" getter="">
</member>
+ <member name="variant/thread_support" type="bool" setter="" getter="">
+ If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites).
+ If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website.
+ </member>
<member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
</member>
<member name="vram_texture_compression/for_mobile" type="bool" setter="" getter="">
diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc
index cd94b779c0..b20a3a46b9 100644
--- a/platform/web/dom_keys.inc
+++ b/platform/web/dom_keys.inc
@@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
return Key::NONE;
#undef DOM2GODOT
}
+
+KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) {
+#define DOM2GODOT(m_str, m_godot_code) \
+ if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \
+ return KeyLocation::m_godot_code; \
+ }
+
+ DOM2GODOT("AltLeft", LEFT);
+ DOM2GODOT("AltRight", RIGHT);
+ DOM2GODOT("ControlLeft", LEFT);
+ DOM2GODOT("ControlRight", RIGHT);
+ DOM2GODOT("MetaLeft", LEFT);
+ DOM2GODOT("MetaRight", RIGHT);
+ DOM2GODOT("OSLeft", LEFT);
+ DOM2GODOT("OSRight", RIGHT);
+ DOM2GODOT("ShiftLeft", LEFT);
+ DOM2GODOT("ShiftRight", RIGHT);
+
+ return KeyLocation::UNSPECIFIED;
+#undef DOM2GODOT
+}
diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py
index ec33397842..3ba133c9a1 100644
--- a/platform/web/emscripten_helpers.py
+++ b/platform/web/emscripten_helpers.py
@@ -4,7 +4,12 @@ from SCons.Util import WhereIs
def run_closure_compiler(target, source, env, for_signature):
- closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler")
+ closure_bin = os.path.join(
+ os.path.dirname(WhereIs("emcc")),
+ "node_modules",
+ ".bin",
+ "google-closure-compiler",
+ )
cmd = [WhereIs("node"), closure_bin]
cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"])
for f in env["JSEXTERNS"]:
@@ -31,27 +36,29 @@ def get_build_version():
return v
-def create_engine_file(env, target, source, externs):
+def create_engine_file(env, target, source, externs, threads_enabled):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
- return env.Textfile(target, [env.File(s) for s in source])
+ subst_dict = {"___GODOT_THREADS_ENABLED": "true" if threads_enabled else "false"}
+ return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict)
def create_template_zip(env, js, wasm, worker, side):
binary_name = "godot.editor" if env.editor_build else "godot"
- zip_dir = env.Dir("#bin/.web_zip")
+ zip_dir = env.Dir(env.GetTemplateZipPath())
in_files = [
js,
wasm,
- worker,
"#platform/web/js/libs/audio.worklet.js",
]
out_files = [
zip_dir.File(binary_name + ".js"),
zip_dir.File(binary_name + ".wasm"),
- zip_dir.File(binary_name + ".worker.js"),
zip_dir.File(binary_name + ".audio.worklet.js"),
]
+ if env["threads"]:
+ in_files.append(worker)
+ out_files.append(zip_dir.File(binary_name + ".worker.js"))
# Dynamic linking (extensions) specific.
if env["dlink_enabled"]:
in_files.append(side) # Side wasm (contains the actual Godot code).
@@ -65,18 +72,20 @@ def create_template_zip(env, js, wasm, worker, side):
"godot.editor.html",
"offline.html",
"godot.editor.js",
- "godot.editor.worker.js",
"godot.editor.audio.worklet.js",
"logo.svg",
"favicon.png",
]
+ if env["threads"]:
+ cache.append("godot.editor.worker.js")
opt_cache = ["godot.editor.wasm"]
subst_dict = {
- "@GODOT_VERSION@": get_build_version(),
- "@GODOT_NAME@": "GodotEngine",
- "@GODOT_CACHE@": json.dumps(cache),
- "@GODOT_OPT_CACHE@": json.dumps(opt_cache),
- "@GODOT_OFFLINE_PAGE@": "offline.html",
+ "___GODOT_VERSION___": get_build_version(),
+ "___GODOT_NAME___": "GodotEngine",
+ "___GODOT_CACHE___": json.dumps(cache),
+ "___GODOT_OPT_CACHE___": json.dumps(opt_cache),
+ "___GODOT_OFFLINE_PAGE___": "offline.html",
+ "___GODOT_THREADS_ENABLED___": "true" if env["threads"] else "false",
}
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
in_files.append(html)
@@ -88,7 +97,9 @@ def create_template_zip(env, js, wasm, worker, side):
out_files.append(zip_dir.File("favicon.png"))
# PWA
service_worker = env.Substfile(
- target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict
+ target="#bin/godot${PROGSUFFIX}.service.worker.js",
+ source=service_worker,
+ SUBST_DICT=subst_dict,
)
in_files.append(service_worker)
out_files.append(zip_dir.File("service.worker.js"))
@@ -115,6 +126,10 @@ def create_template_zip(env, js, wasm, worker, side):
)
+def get_template_zip_path(env):
+ return "#bin/.web_zip"
+
+
def add_js_libraries(env, libraries):
env.Append(JS_LIBS=env.File(libraries))
diff --git a/platform/web/export/editor_http_server.cpp b/platform/web/export/editor_http_server.cpp
new file mode 100644
index 0000000000..0ccd4b8782
--- /dev/null
+++ b/platform/web/export/editor_http_server.cpp
@@ -0,0 +1,255 @@
+/**************************************************************************/
+/* editor_http_server.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#include "editor_http_server.h"
+
+void EditorHTTPServer::_server_thread_poll(void *data) {
+ EditorHTTPServer *web_server = static_cast<EditorHTTPServer *>(data);
+ while (!web_server->server_quit.get()) {
+ OS::get_singleton()->delay_usec(6900);
+ {
+ MutexLock lock(web_server->server_lock);
+ web_server->_poll();
+ }
+ }
+}
+
+void EditorHTTPServer::_clear_client() {
+ peer = Ref<StreamPeer>();
+ tls = Ref<StreamPeerTLS>();
+ tcp = Ref<StreamPeerTCP>();
+ memset(req_buf, 0, sizeof(req_buf));
+ time = 0;
+ req_pos = 0;
+}
+
+void EditorHTTPServer::_set_internal_certs(Ref<Crypto> p_crypto) {
+ const String cache_path = EditorPaths::get_singleton()->get_cache_dir();
+ const String key_path = cache_path.path_join("html5_server.key");
+ const String crt_path = cache_path.path_join("html5_server.crt");
+ bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);
+ if (!regen) {
+ key = Ref<CryptoKey>(CryptoKey::create());
+ cert = Ref<X509Certificate>(X509Certificate::create());
+ if (key->load(key_path) != OK || cert->load(crt_path) != OK) {
+ regen = true;
+ }
+ }
+ if (regen) {
+ key = p_crypto->generate_rsa(2048);
+ key->save(key_path);
+ cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000");
+ cert->save(crt_path);
+ }
+}
+
+void EditorHTTPServer::_send_response() {
+ Vector<String> psa = String((char *)req_buf).split("\r\n");
+ int len = psa.size();
+ ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");
+
+ Vector<String> req = psa[0].split(" ", false);
+ ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code.");
+
+ // Wrong protocol
+ ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
+
+ const int query_index = req[1].find_char('?');
+ const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index);
+
+ const String req_file = path.get_file();
+ const String req_ext = path.get_extension();
+ const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web");
+ const String filepath = cache_path.path_join(req_file);
+
+ if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
+ String s = "HTTP/1.1 404 Not Found\r\n";
+ s += "Connection: Close\r\n";
+ s += "\r\n";
+ CharString cs = s.utf8();
+ peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
+ return;
+ }
+ const String ctype = mimes[req_ext];
+
+ 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";
+ s += "Access-Control-Allow-Origin: *\r\n";
+ s += "Cross-Origin-Opener-Policy: same-origin\r\n";
+ s += "Cross-Origin-Embedder-Policy: require-corp\r\n";
+ s += "Cache-Control: no-store, max-age=0\r\n";
+ s += "\r\n";
+ CharString cs = s.utf8();
+ Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
+ if (err != OK) {
+ ERR_FAIL();
+ }
+
+ while (true) {
+ uint8_t bytes[4096];
+ uint64_t read = f->get_buffer(bytes, 4096);
+ if (read == 0) {
+ break;
+ }
+ err = peer->put_data(bytes, read);
+ if (err != OK) {
+ ERR_FAIL();
+ }
+ }
+}
+
+void EditorHTTPServer::_poll() {
+ if (!server->is_listening()) {
+ return;
+ }
+ if (tcp.is_null()) {
+ if (!server->is_connection_available()) {
+ return;
+ }
+ tcp = server->take_connection();
+ peer = tcp;
+ time = OS::get_singleton()->get_ticks_usec();
+ }
+ if (OS::get_singleton()->get_ticks_usec() - time > 1000000) {
+ _clear_client();
+ return;
+ }
+ if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ return;
+ }
+
+ if (use_tls) {
+ if (tls.is_null()) {
+ tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
+ peer = tls;
+ if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) {
+ _clear_client();
+ return;
+ }
+ }
+ tls->poll();
+ if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
+ // Still handshaking, keep waiting.
+ return;
+ }
+ if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
+ _clear_client();
+ return;
+ }
+ }
+
+ while (true) {
+ char *r = (char *)req_buf;
+ int l = req_pos - 1;
+ if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
+ _send_response();
+ _clear_client();
+ return;
+ }
+
+ int read = 0;
+ ERR_FAIL_COND(req_pos >= 4096);
+ Error err = peer->get_partial_data(&req_buf[req_pos], 1, read);
+ if (err != OK) {
+ // Got an error
+ _clear_client();
+ return;
+ } else if (read != 1) {
+ // Busy, wait next poll
+ return;
+ }
+ req_pos += read;
+ }
+}
+
+void EditorHTTPServer::stop() {
+ server_quit.set(true);
+ if (server_thread.is_started()) {
+ server_thread.wait_to_finish();
+ }
+ if (server.is_valid()) {
+ server->stop();
+ }
+ _clear_client();
+}
+
+Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) {
+ MutexLock lock(server_lock);
+ if (server->is_listening()) {
+ return ERR_ALREADY_IN_USE;
+ }
+ use_tls = p_use_tls;
+ if (use_tls) {
+ Ref<Crypto> crypto = Crypto::create();
+ if (crypto.is_null()) {
+ return ERR_UNAVAILABLE;
+ }
+ if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) {
+ key = Ref<CryptoKey>(CryptoKey::create());
+ Error err = key->load(p_tls_key);
+ ERR_FAIL_COND_V(err != OK, err);
+ cert = Ref<X509Certificate>(X509Certificate::create());
+ err = cert->load(p_tls_cert);
+ ERR_FAIL_COND_V(err != OK, err);
+ } else {
+ _set_internal_certs(crypto);
+ }
+ }
+ Error err = server->listen(p_port, p_address);
+ if (err == OK) {
+ server_quit.set(false);
+ server_thread.start(_server_thread_poll, this);
+ }
+ return err;
+}
+
+bool EditorHTTPServer::is_listening() const {
+ MutexLock lock(server_lock);
+ return server->is_listening();
+}
+
+EditorHTTPServer::EditorHTTPServer() {
+ mimes["html"] = "text/html";
+ mimes["js"] = "application/javascript";
+ mimes["json"] = "application/json";
+ mimes["pck"] = "application/octet-stream";
+ mimes["png"] = "image/png";
+ mimes["svg"] = "image/svg";
+ mimes["wasm"] = "application/wasm";
+ server.instantiate();
+ stop();
+}
+
+EditorHTTPServer::~EditorHTTPServer() {
+ stop();
+}
diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h
index 3f87288537..8f5f69cc1e 100644
--- a/platform/web/export/editor_http_server.h
+++ b/platform/web/export/editor_http_server.h
@@ -51,199 +51,24 @@ private:
uint8_t req_buf[4096];
int req_pos = 0;
- void _clear_client() {
- peer = Ref<StreamPeer>();
- tls = Ref<StreamPeerTLS>();
- tcp = Ref<StreamPeerTCP>();
- memset(req_buf, 0, sizeof(req_buf));
- time = 0;
- req_pos = 0;
- }
+ SafeNumeric<bool> server_quit;
+ Mutex server_lock;
+ Thread server_thread;
- void _set_internal_certs(Ref<Crypto> p_crypto) {
- const String cache_path = EditorPaths::get_singleton()->get_cache_dir();
- const String key_path = cache_path.path_join("html5_server.key");
- const String crt_path = cache_path.path_join("html5_server.crt");
- bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);
- if (!regen) {
- key = Ref<CryptoKey>(CryptoKey::create());
- cert = Ref<X509Certificate>(X509Certificate::create());
- if (key->load(key_path) != OK || cert->load(crt_path) != OK) {
- regen = true;
- }
- }
- if (regen) {
- key = p_crypto->generate_rsa(2048);
- key->save(key_path);
- cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000");
- cert->save(crt_path);
- }
- }
+ void _clear_client();
+ void _set_internal_certs(Ref<Crypto> p_crypto);
+ void _send_response();
+ void _poll();
-public:
- EditorHTTPServer() {
- mimes["html"] = "text/html";
- mimes["js"] = "application/javascript";
- mimes["json"] = "application/json";
- mimes["pck"] = "application/octet-stream";
- mimes["png"] = "image/png";
- mimes["svg"] = "image/svg";
- mimes["wasm"] = "application/wasm";
- server.instantiate();
- stop();
- }
-
- void stop() {
- server->stop();
- _clear_client();
- }
-
- Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) {
- use_tls = p_use_tls;
- if (use_tls) {
- Ref<Crypto> crypto = Crypto::create();
- if (crypto.is_null()) {
- return ERR_UNAVAILABLE;
- }
- if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) {
- key = Ref<CryptoKey>(CryptoKey::create());
- Error err = key->load(p_tls_key);
- ERR_FAIL_COND_V(err != OK, err);
- cert = Ref<X509Certificate>(X509Certificate::create());
- err = cert->load(p_tls_cert);
- ERR_FAIL_COND_V(err != OK, err);
- } else {
- _set_internal_certs(crypto);
- }
- }
- return server->listen(p_port, p_address);
- }
-
- bool is_listening() const {
- return server->is_listening();
- }
-
- void _send_response() {
- Vector<String> psa = String((char *)req_buf).split("\r\n");
- int len = psa.size();
- ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4.");
-
- Vector<String> req = psa[0].split(" ", false);
- ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code.");
-
- // Wrong protocol
- ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
-
- const int query_index = req[1].find_char('?');
- const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index);
+ static void _server_thread_poll(void *data);
- const String req_file = path.get_file();
- const String req_ext = path.get_extension();
- const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web");
- const String filepath = cache_path.path_join(req_file);
-
- if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
- String s = "HTTP/1.1 404 Not Found\r\n";
- s += "Connection: Close\r\n";
- s += "\r\n";
- CharString cs = s.utf8();
- peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
- return;
- }
- const String ctype = mimes[req_ext];
-
- 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";
- s += "Access-Control-Allow-Origin: *\r\n";
- s += "Cross-Origin-Opener-Policy: same-origin\r\n";
- s += "Cross-Origin-Embedder-Policy: require-corp\r\n";
- s += "Cache-Control: no-store, max-age=0\r\n";
- s += "\r\n";
- CharString cs = s.utf8();
- Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
- if (err != OK) {
- ERR_FAIL();
- }
-
- while (true) {
- uint8_t bytes[4096];
- uint64_t read = f->get_buffer(bytes, 4096);
- if (read == 0) {
- break;
- }
- err = peer->put_data(bytes, read);
- if (err != OK) {
- ERR_FAIL();
- }
- }
- }
-
- void poll() {
- if (!server->is_listening()) {
- return;
- }
- if (tcp.is_null()) {
- if (!server->is_connection_available()) {
- return;
- }
- tcp = server->take_connection();
- peer = tcp;
- time = OS::get_singleton()->get_ticks_usec();
- }
- if (OS::get_singleton()->get_ticks_usec() - time > 1000000) {
- _clear_client();
- return;
- }
- if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- return;
- }
-
- if (use_tls) {
- if (tls.is_null()) {
- tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
- peer = tls;
- if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) {
- _clear_client();
- return;
- }
- }
- tls->poll();
- if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) {
- // Still handshaking, keep waiting.
- return;
- }
- if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
- _clear_client();
- return;
- }
- }
-
- while (true) {
- char *r = (char *)req_buf;
- int l = req_pos - 1;
- if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
- _send_response();
- _clear_client();
- return;
- }
+public:
+ EditorHTTPServer();
+ ~EditorHTTPServer();
- int read = 0;
- ERR_FAIL_COND(req_pos >= 4096);
- Error err = peer->get_partial_data(&req_buf[req_pos], 1, read);
- if (err != OK) {
- // Got an error
- _clear_client();
- return;
- } else if (read != 1) {
- // Busy, wait next poll
- return;
- }
- req_pos += read;
- }
- }
+ void stop();
+ Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert);
+ bool is_listening() const;
};
#endif // WEB_EDITOR_HTTP_SERVER_H
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index a70812cf5b..706bfca8ba 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -34,11 +34,11 @@
#include "run_icon_svg.gen.h"
#include "core/config/project_settings.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/export/editor_export.h"
#include "editor/import/resource_importer_texture_settings.h"
+#include "editor/themes/editor_scale.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For mono and svg.
@@ -112,7 +112,7 @@ Error EditorExportPlatformWeb::_write_or_error(const uint8_t *p_content, int p_s
return OK;
}
-void EditorExportPlatformWeb::_replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template) {
+void EditorExportPlatformWeb::_replace_strings(const 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");
@@ -169,6 +169,13 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito
replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name");
replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include;
replaces["$GODOT_CONFIG"] = str_config;
+
+ if (p_preset->get("variant/thread_support")) {
+ replaces["$GODOT_THREADS_ENABLED"] = "true";
+ } else {
+ replaces["$GODOT_THREADS_ENABLED"] = "false";
+ }
+
_replace_strings(replaces, p_html);
}
@@ -216,9 +223,9 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
const String name = p_path.get_file().get_basename();
bool extensions = (bool)p_preset->get("variant/extensions_support");
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";
+ 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";
// Files cached during worker install.
Array cache_files;
@@ -231,7 +238,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
}
cache_files.push_back(name + ".worker.js");
cache_files.push_back(name + ".audio.worklet.js");
- replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string();
+ replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
// Heavy files that are cached on demand.
Array opt_cache_files;
@@ -243,7 +250,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
opt_cache_files.push_back(p_shared_objects[i].path.get_file());
}
}
- replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string();
+ replaces["___GODOT_OPT_CACHE___"] = Variant(opt_cache_files).to_json_string();
const String sw_path = dir.path_join(name + ".service.worker.js");
Vector<uint8_t> sw;
@@ -335,6 +342,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type.
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), true)); // Thread support (i.e. run with or without COEP/COOP headers).
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@@ -377,10 +385,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp
String err;
bool valid = false;
bool extensions = (bool)p_preset->get("variant/extensions_support");
+ bool thread_support = (bool)p_preset->get("variant/thread_support");
// Look for export templates (first official, and if defined custom templates).
- bool dvalid = exists_export_template(_get_template_name(extensions, true), &err);
- bool rvalid = exists_export_template(_get_template_name(extensions, false), &err);
+ bool dvalid = exists_export_template(_get_template_name(extensions, thread_support, true), &err);
+ bool rvalid = exists_export_template(_get_template_name(extensions, thread_support, false), &err);
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@@ -454,7 +463,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p
template_path = template_path.strip_edges();
if (template_path.is_empty()) {
bool extensions = (bool)p_preset->get("variant/extensions_support");
- template_path = find_export_template(_get_template_name(extensions, p_debug));
+ bool thread_support = (bool)p_preset->get("variant/thread_support");
+ template_path = find_export_template(_get_template_name(extensions, thread_support, p_debug));
}
if (!template_path.is_empty() && !FileAccess::exists(template_path)) {
@@ -574,7 +584,6 @@ bool EditorExportPlatformWeb::poll_export() {
menu_options = preset.is_valid();
if (server->is_listening()) {
if (menu_options == 0) {
- MutexLock lock(server_lock);
server->stop();
} else {
menu_options += 1;
@@ -593,7 +602,6 @@ int EditorExportPlatformWeb::get_options_count() const {
Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
if (p_option == 1) {
- MutexLock lock(server_lock);
server->stop();
return OK;
}
@@ -643,12 +651,8 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
const String tls_cert = EDITOR_GET("export/web/tls_certificate");
// Restart server.
- {
- MutexLock lock(server_lock);
-
- server->stop();
- err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert);
- }
+ server->stop();
+ err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert);
if (err != OK) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err));
return err;
@@ -664,21 +668,9 @@ Ref<Texture2D> EditorExportPlatformWeb::get_run_icon() const {
return run_icon;
}
-void EditorExportPlatformWeb::_server_thread_poll(void *data) {
- EditorExportPlatformWeb *ej = static_cast<EditorExportPlatformWeb *>(data);
- while (!ej->server_quit) {
- OS::get_singleton()->delay_usec(6900);
- {
- MutexLock lock(ej->server_lock);
- ej->server->poll();
- }
- }
-}
-
EditorExportPlatformWeb::EditorExportPlatformWeb() {
if (EditorNode::get_singleton()) {
server.instantiate();
- server_thread.start(_server_thread_poll, this);
#ifdef MODULE_SVG_ENABLED
Ref<Image> img = memnew(Image);
@@ -701,11 +693,4 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() {
}
EditorExportPlatformWeb::~EditorExportPlatformWeb() {
- if (server.is_valid()) {
- server->stop();
- }
- server_quit = true;
- if (server_thread.is_started()) {
- server_thread.wait_to_finish();
- }
}
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 887000ac45..952d03cdb4 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -52,15 +52,15 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
int menu_options = 0;
Ref<EditorHTTPServer> server;
- bool server_quit = false;
- Mutex server_lock;
- Thread server_thread;
- String _get_template_name(bool p_extension, bool p_debug) const {
+ String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const {
String name = "web";
if (p_extension) {
name += "_dlink";
}
+ if (!p_thread_support) {
+ name += "_nothreads";
+ }
if (p_debug) {
name += "_debug.zip";
} else {
@@ -90,14 +90,12 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
}
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
- void _replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template);
+ void _replace_strings(const 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);
Error _write_or_error(const uint8_t *p_content, int p_len, String p_path);
- static void _server_thread_poll(void *data);
-
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
diff --git a/platform/web/js/engine/config.js b/platform/web/js/engine/config.js
index 0b6626968e..a7134da6fa 100644
--- a/platform/web/js/engine/config.js
+++ b/platform/web/js/engine/config.js
@@ -19,7 +19,7 @@ const EngineConfig = {}; // eslint-disable-line no-unused-vars
const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars
const cfg = /** @lends {InternalConfig.prototype} */ {
/**
- * Whether the unload the engine automatically after the instance is initialized.
+ * Whether to unload the engine automatically after the instance is initialized.
*
* @memberof EngineConfig
* @default
diff --git a/platform/web/js/engine/features.js b/platform/web/js/engine/features.js
index b7c6c9d445..81bc82f3c6 100644
--- a/platform/web/js/engine/features.js
+++ b/platform/web/js/engine/features.js
@@ -72,8 +72,14 @@ const Features = { // eslint-disable-line no-unused-vars
*
* @returns {Array<string>} A list of human-readable missing features.
* @function Engine.getMissingFeatures
+ * @typedef {{ threads: boolean }} SupportedFeatures
+ * @param {SupportedFeatures} supportedFeatures
*/
- getMissingFeatures: function () {
+ getMissingFeatures: function (supportedFeatures = {}) {
+ const {
+ threads: supportsThreads = true,
+ } = supportedFeatures;
+
const missing = [];
if (!Features.isWebGLAvailable(2)) {
missing.push('WebGL2 - Check web browser configuration and hardware support');
@@ -84,12 +90,16 @@ const Features = { // eslint-disable-line no-unused-vars
if (!Features.isSecureContext()) {
missing.push('Secure Context - Check web server configuration (use HTTPS)');
}
- if (!Features.isCrossOriginIsolated()) {
- missing.push('Cross Origin Isolation - Check web server configuration (send correct headers)');
- }
- if (!Features.isSharedArrayBufferAvailable()) {
- missing.push('SharedArrayBuffer - Check web server configuration (send correct headers)');
+
+ if (supportsThreads) {
+ if (!Features.isCrossOriginIsolated()) {
+ missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.');
+ }
+ if (!Features.isSharedArrayBufferAvailable()) {
+ missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.');
+ }
}
+
// Audio is normally optional since we have a dummy fallback.
return missing;
},
diff --git a/platform/web/js/libs/audio.worklet.js b/platform/web/js/libs/audio.worklet.js
index 89b581b3d6..3b94cab85c 100644
--- a/platform/web/js/libs/audio.worklet.js
+++ b/platform/web/js/libs/audio.worklet.js
@@ -167,7 +167,7 @@ class GodotProcessor extends AudioWorkletProcessor {
GodotProcessor.write_input(this.input_buffer, input);
this.input.write(this.input_buffer);
} else {
- this.port.postMessage('Input buffer is full! Skipping input frame.');
+ // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.
}
}
const process_output = GodotProcessor.array_has_data(outputs);
@@ -184,7 +184,7 @@ class GodotProcessor extends AudioWorkletProcessor {
this.port.postMessage({ 'cmd': 'read', 'data': chunk });
}
} else {
- this.port.postMessage('Output buffer has not enough frames! Skipping output frame.');
+ // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.
}
}
this.process_notify();