feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
from methods import print_error
|
||||
|
||||
|
|
@ -21,20 +22,26 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS:
|
|||
|
||||
web_files = [
|
||||
"audio_driver_web.cpp",
|
||||
"webmidi_driver.cpp",
|
||||
"display_server_web.cpp",
|
||||
"http_client_web.cpp",
|
||||
"javascript_bridge_singleton.cpp",
|
||||
"web_main.cpp",
|
||||
"ip_web.cpp",
|
||||
"net_socket_web.cpp",
|
||||
"os_web.cpp",
|
||||
"api/web_tools_editor_plugin.cpp",
|
||||
]
|
||||
|
||||
if env["target"] == "editor":
|
||||
env.add_source_files(web_files, "editor/*.cpp")
|
||||
|
||||
sys_env = env.Clone()
|
||||
sys_env.AddJSLibraries(
|
||||
[
|
||||
"js/libs/library_godot_audio.js",
|
||||
"js/libs/library_godot_display.js",
|
||||
"js/libs/library_godot_fetch.js",
|
||||
"js/libs/library_godot_webmidi.js",
|
||||
"js/libs/library_godot_os.js",
|
||||
"js/libs/library_godot_runtime.js",
|
||||
"js/libs/library_godot_input.js",
|
||||
|
|
@ -58,7 +65,7 @@ for ext in sys_env["JS_EXTERNS"]:
|
|||
sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.abspath
|
||||
|
||||
build = []
|
||||
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm", "#bin/godot${PROGSUFFIX}.worker.js"]
|
||||
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
|
||||
if env["dlink_enabled"]:
|
||||
# Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
|
||||
sys_env["LIBS"] = []
|
||||
|
|
@ -107,6 +114,5 @@ js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFI
|
|||
|
||||
# 0 - unwrapped js file (use wrapped one instead)
|
||||
# 1 - wasm file
|
||||
# 2 - worker file
|
||||
# 3 - wasm side (when dlink is enabled).
|
||||
env.CreateTemplateZip(js_wrapped, build[1], build[2], build[3] if len(build) > 3 else None)
|
||||
# 2 - wasm side (when dlink is enabled).
|
||||
env.CreateTemplateZip(js_wrapped, build[1], build[2] if len(build) > 2 else None)
|
||||
|
|
|
|||
|
|
@ -31,14 +31,12 @@
|
|||
#include "api.h"
|
||||
|
||||
#include "javascript_bridge_singleton.h"
|
||||
#include "web_tools_editor_plugin.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
static JavaScriptBridge *javascript_bridge_singleton;
|
||||
|
||||
void register_web_api() {
|
||||
WebToolsEditorPlugin::initialize();
|
||||
GDREGISTER_ABSTRACT_CLASS(JavaScriptObject);
|
||||
GDREGISTER_ABSTRACT_CLASS(JavaScriptBridge);
|
||||
javascript_bridge_singleton = memnew(JavaScriptBridge);
|
||||
|
|
@ -66,6 +64,8 @@ void JavaScriptBridge::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScriptBridge::eval, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_interface", "interface"), &JavaScriptBridge::get_interface);
|
||||
ClassDB::bind_method(D_METHOD("create_callback", "callable"), &JavaScriptBridge::create_callback);
|
||||
ClassDB::bind_method(D_METHOD("is_js_buffer", "javascript_object"), &JavaScriptBridge::is_js_buffer);
|
||||
ClassDB::bind_method(D_METHOD("js_buffer_to_packed_byte_array", "javascript_buffer"), &JavaScriptBridge::js_buffer_to_packed_byte_array);
|
||||
{
|
||||
MethodInfo mi;
|
||||
mi.name = "create_object";
|
||||
|
|
@ -93,13 +93,21 @@ Ref<JavaScriptObject> JavaScriptBridge::create_callback(const Callable &p_callab
|
|||
return Ref<JavaScriptObject>();
|
||||
}
|
||||
|
||||
bool JavaScriptBridge::is_js_buffer(Ref<JavaScriptObject> p_js_obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PackedByteArray JavaScriptBridge::js_buffer_to_packed_byte_array(Ref<JavaScriptObject> p_js_obj) {
|
||||
return PackedByteArray();
|
||||
}
|
||||
|
||||
Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
if (p_argcount < 1) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
||||
r_error.expected = 1;
|
||||
return Ref<JavaScriptObject>();
|
||||
}
|
||||
if (p_args[0]->get_type() != Variant::STRING) {
|
||||
if (!p_args[0]->is_string()) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::STRING;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ public:
|
|||
Variant eval(const String &p_code, bool p_use_global_exec_context = false);
|
||||
Ref<JavaScriptObject> get_interface(const String &p_interface);
|
||||
Ref<JavaScriptObject> create_callback(const Callable &p_callable);
|
||||
bool is_js_buffer(Ref<JavaScriptObject> p_js_obj);
|
||||
PackedByteArray js_buffer_to_packed_byte_array(Ref<JavaScriptObject> p_js_obj);
|
||||
Variant _create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
|
||||
void download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime = "application/octet-stream");
|
||||
bool pwa_needs_update() const;
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ void AudioDriverWeb::start_sample_playback(const Ref<AudioSamplePlayback> &p_pla
|
|||
itos(p_playback->stream->get_instance_id()).utf8().get_data(),
|
||||
AudioServer::get_singleton()->get_bus_index(p_playback->bus),
|
||||
p_playback->offset,
|
||||
p_playback->pitch_scale,
|
||||
volume_ptrw);
|
||||
}
|
||||
|
||||
|
|
@ -312,6 +313,11 @@ bool AudioDriverWeb::is_sample_playback_active(const Ref<AudioSamplePlayback> &p
|
|||
return godot_audio_sample_is_active(itos(p_playback->get_instance_id()).utf8().get_data()) != 0;
|
||||
}
|
||||
|
||||
double AudioDriverWeb::get_sample_playback_position(const Ref<AudioSamplePlayback> &p_playback) {
|
||||
ERR_FAIL_COND_V_MSG(p_playback.is_null(), false, "Parameter p_playback is null.");
|
||||
return godot_audio_get_sample_playback_position(itos(p_playback->get_instance_id()).utf8().get_data());
|
||||
}
|
||||
|
||||
void AudioDriverWeb::update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale) {
|
||||
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
|
||||
godot_audio_sample_update_pitch_scale(
|
||||
|
|
@ -473,6 +479,8 @@ void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) {
|
|||
driver->_audio_driver_capture(p_pos, p_samples);
|
||||
}
|
||||
|
||||
#endif // THREADS_ENABLED
|
||||
|
||||
/// ScriptProcessorNode implementation
|
||||
AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr;
|
||||
|
||||
|
|
@ -491,5 +499,3 @@ Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int 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
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ public:
|
|||
virtual void stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
|
||||
virtual void set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) override;
|
||||
virtual bool is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) override;
|
||||
virtual double get_sample_playback_position(const Ref<AudioSamplePlayback> &p_playback) override;
|
||||
virtual void update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale = 0.0f) override;
|
||||
virtual void set_sample_playback_bus_volumes_linear(const Ref<AudioSamplePlayback> &p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) override;
|
||||
|
||||
|
|
@ -168,6 +169,8 @@ public:
|
|||
AudioDriverWorklet() { singleton = this; }
|
||||
};
|
||||
|
||||
#endif // THREADS_ENABLED
|
||||
|
||||
class AudioDriverScriptProcessor : public AudioDriverWeb {
|
||||
private:
|
||||
static void _process_callback();
|
||||
|
|
@ -177,7 +180,6 @@ private:
|
|||
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"; }
|
||||
|
|
@ -190,6 +192,4 @@ public:
|
|||
AudioDriverScriptProcessor() { singleton = this; }
|
||||
};
|
||||
|
||||
#endif // THREADS_ENABLED
|
||||
|
||||
#endif // AUDIO_DRIVER_WEB_H
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ from emscripten_helpers import (
|
|||
)
|
||||
from SCons.Util import WhereIs
|
||||
|
||||
from methods import get_compiler_version, print_error, print_warning
|
||||
from methods import get_compiler_version, print_error, print_info, print_warning
|
||||
from platform_methods import validate_arch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from SCons.Script.SConscript import SConsEnvironment
|
||||
|
|
@ -27,6 +28,11 @@ def can_build():
|
|||
return WhereIs("emcc") is not None
|
||||
|
||||
|
||||
def get_tools(env: "SConsEnvironment"):
|
||||
# Use generic POSIX build toolchain for Emscripten.
|
||||
return ["cc", "c++", "ar", "link", "textfile", "zip"]
|
||||
|
||||
|
||||
def get_opts():
|
||||
from SCons.Variables import BoolVariable
|
||||
|
||||
|
|
@ -86,12 +92,7 @@ def get_flags():
|
|||
def configure(env: "SConsEnvironment"):
|
||||
# Validate arch.
|
||||
supported_arches = ["wasm32"]
|
||||
if env["arch"] not in supported_arches:
|
||||
print_error(
|
||||
'Unsupported CPU architecture "%s" for Web. Supported architectures are: %s.'
|
||||
% (env["arch"], ", ".join(supported_arches))
|
||||
)
|
||||
sys.exit(255)
|
||||
validate_arch(env["arch"], get_name(), supported_arches)
|
||||
|
||||
try:
|
||||
env["initial_memory"] = int(env["initial_memory"])
|
||||
|
|
@ -111,7 +112,7 @@ def configure(env: "SConsEnvironment"):
|
|||
env.Append(LINKFLAGS=["-sASSERTIONS=1"])
|
||||
|
||||
if env.editor_build and env["initial_memory"] < 64:
|
||||
print("Note: Forcing `initial_memory=64` as it is required for the web editor.")
|
||||
print_info("Forcing `initial_memory=64` as it is required for the web editor.")
|
||||
env["initial_memory"] = 64
|
||||
|
||||
env.Append(LINKFLAGS=["-sINITIAL_MEMORY=%sMB" % env["initial_memory"]])
|
||||
|
|
@ -121,8 +122,8 @@ def configure(env: "SConsEnvironment"):
|
|||
|
||||
# LTO
|
||||
|
||||
if env["lto"] == "auto": # Full LTO for production.
|
||||
env["lto"] = "full"
|
||||
if env["lto"] == "auto": # Enable LTO for production.
|
||||
env["lto"] = "thin"
|
||||
|
||||
if env["lto"] != "none":
|
||||
if env["lto"] == "thin":
|
||||
|
|
@ -199,8 +200,13 @@ def configure(env: "SConsEnvironment"):
|
|||
cc_version = get_compiler_version(env)
|
||||
cc_semver = (cc_version["major"], cc_version["minor"], cc_version["patch"])
|
||||
|
||||
# Minimum emscripten requirements.
|
||||
if cc_semver < (3, 1, 62):
|
||||
print_error("The minimum emscripten version to build Godot is 3.1.62, detected: %s.%s.%s" % cc_semver)
|
||||
sys.exit(255)
|
||||
|
||||
env.Prepend(CPPPATH=["#platform/web"])
|
||||
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])
|
||||
env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED", "UNIX_SOCKET_UNAVAILABLE"])
|
||||
|
||||
if env["opengl3"]:
|
||||
env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
|
||||
|
|
@ -210,14 +216,12 @@ def configure(env: "SConsEnvironment"):
|
|||
env.Append(LINKFLAGS=["-sOFFSCREEN_FRAMEBUFFER=1"])
|
||||
# Disables the use of *glGetProcAddress() which is inefficient.
|
||||
# See https://emscripten.org/docs/tools_reference/settings_reference.html#gl-enable-get-proc-address
|
||||
if cc_semver >= (3, 1, 51):
|
||||
env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=0"])
|
||||
env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=0"])
|
||||
|
||||
if env["javascript_eval"]:
|
||||
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
|
||||
|
||||
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"])])
|
||||
env.Append(LINKFLAGS=["-s%s=%sKB" % ("STACK_SIZE", env["stack_size"])])
|
||||
|
||||
if env["threads"]:
|
||||
# Thread support (via SharedArrayBuffer).
|
||||
|
|
@ -237,30 +241,21 @@ def configure(env: "SConsEnvironment"):
|
|||
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"])
|
||||
env.Append(LINKFLAGS=["-Wl,-u,_emscripten_run_callback_on_thread"])
|
||||
|
||||
if env["dlink_enabled"]:
|
||||
if env["proxy_to_pthread"]:
|
||||
print_warning("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.")
|
||||
env["proxy_to_pthread"] = False
|
||||
|
||||
if cc_semver < (3, 1, 14):
|
||||
print_error("GDExtension support requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver)
|
||||
sys.exit(255)
|
||||
|
||||
env.Append(CCFLAGS=["-sSIDE_MODULE=2"])
|
||||
env.Append(LINKFLAGS=["-sSIDE_MODULE=2"])
|
||||
env.Append(CCFLAGS=["-fvisibility=hidden"])
|
||||
env.Append(LINKFLAGS=["-fvisibility=hidden"])
|
||||
env.extra_suffix = ".dlink" + env.extra_suffix
|
||||
|
||||
# WASM_BIGINT is needed since emscripten ≥ 3.1.41
|
||||
needs_wasm_bigint = cc_semver >= (3, 1, 41)
|
||||
env.Append(LINKFLAGS=["-sWASM_BIGINT"])
|
||||
|
||||
# Run the main application in a web worker
|
||||
if env["proxy_to_pthread"]:
|
||||
|
|
@ -269,11 +264,6 @@ def configure(env: "SConsEnvironment"):
|
|||
env.Append(LINKFLAGS=["-sEXPORTED_RUNTIME_METHODS=['_emscripten_proxy_main']"])
|
||||
# https://github.com/emscripten-core/emscripten/issues/18034#issuecomment-1277561925
|
||||
env.Append(LINKFLAGS=["-sTEXTDECODER=0"])
|
||||
# BigInt support to pass object pointers between contexts
|
||||
needs_wasm_bigint = True
|
||||
|
||||
if needs_wasm_bigint:
|
||||
env.Append(LINKFLAGS=["-sWASM_BIGINT"])
|
||||
|
||||
# Reduce code size by generating less support code (e.g. skip NodeJS support).
|
||||
env.Append(LINKFLAGS=["-sENVIRONMENT=web,worker"])
|
||||
|
|
|
|||
|
|
@ -550,26 +550,47 @@ void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
|
|||
}
|
||||
|
||||
// Mouse mode
|
||||
void DisplayServerWeb::mouse_set_mode(MouseMode p_mode) {
|
||||
ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN, "MOUSE_MODE_CONFINED is not supported for the Web platform.");
|
||||
if (p_mode == mouse_get_mode()) {
|
||||
void DisplayServerWeb::_mouse_update_mode() {
|
||||
MouseMode wanted_mouse_mode = mouse_mode_override_enabled
|
||||
? mouse_mode_override
|
||||
: mouse_mode_base;
|
||||
|
||||
ERR_FAIL_COND_MSG(wanted_mouse_mode == MOUSE_MODE_CONFINED || wanted_mouse_mode == MOUSE_MODE_CONFINED_HIDDEN, "MOUSE_MODE_CONFINED is not supported for the Web platform.");
|
||||
if (wanted_mouse_mode == mouse_get_mode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_mode == MOUSE_MODE_VISIBLE) {
|
||||
if (wanted_mouse_mode == MOUSE_MODE_VISIBLE) {
|
||||
godot_js_display_cursor_set_visible(1);
|
||||
godot_js_display_cursor_lock_set(0);
|
||||
|
||||
} else if (p_mode == MOUSE_MODE_HIDDEN) {
|
||||
} else if (wanted_mouse_mode == MOUSE_MODE_HIDDEN) {
|
||||
godot_js_display_cursor_set_visible(0);
|
||||
godot_js_display_cursor_lock_set(0);
|
||||
|
||||
} else if (p_mode == MOUSE_MODE_CAPTURED) {
|
||||
} else if (wanted_mouse_mode == MOUSE_MODE_CAPTURED) {
|
||||
godot_js_display_cursor_set_visible(1);
|
||||
godot_js_display_cursor_lock_set(1);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayServerWeb::mouse_set_mode(MouseMode p_mode) {
|
||||
ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);
|
||||
|
||||
if (mouse_mode_override_enabled) {
|
||||
mouse_mode_base = p_mode;
|
||||
// No need to update, as override is enabled.
|
||||
return;
|
||||
}
|
||||
if (p_mode == mouse_mode_base && p_mode == mouse_get_mode()) {
|
||||
// No need to update, as it is currently set as the correct mode.
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_mode_base = p_mode;
|
||||
_mouse_update_mode();
|
||||
}
|
||||
|
||||
DisplayServer::MouseMode DisplayServerWeb::mouse_get_mode() const {
|
||||
if (godot_js_display_cursor_is_hidden()) {
|
||||
return MOUSE_MODE_HIDDEN;
|
||||
|
|
@ -581,6 +602,39 @@ DisplayServer::MouseMode DisplayServerWeb::mouse_get_mode() const {
|
|||
return MOUSE_MODE_VISIBLE;
|
||||
}
|
||||
|
||||
void DisplayServerWeb::mouse_set_mode_override(MouseMode p_mode) {
|
||||
ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);
|
||||
|
||||
if (!mouse_mode_override_enabled) {
|
||||
mouse_mode_override = p_mode;
|
||||
// No need to update, as override is not enabled.
|
||||
return;
|
||||
}
|
||||
if (p_mode == mouse_mode_override && p_mode == mouse_get_mode()) {
|
||||
// No need to update, as it is currently set as the correct mode.
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_mode_override = p_mode;
|
||||
_mouse_update_mode();
|
||||
}
|
||||
|
||||
DisplayServer::MouseMode DisplayServerWeb::mouse_get_mode_override() const {
|
||||
return mouse_mode_override;
|
||||
}
|
||||
|
||||
void DisplayServerWeb::mouse_set_mode_override_enabled(bool p_override_enabled) {
|
||||
if (p_override_enabled == mouse_mode_override_enabled) {
|
||||
return;
|
||||
}
|
||||
mouse_mode_override_enabled = p_override_enabled;
|
||||
_mouse_update_mode();
|
||||
}
|
||||
|
||||
bool DisplayServerWeb::mouse_is_mode_override_enabled() const {
|
||||
return mouse_mode_override_enabled;
|
||||
}
|
||||
|
||||
Point2i DisplayServerWeb::mouse_get_position() const {
|
||||
return Input::get_singleton()->get_mouse_position();
|
||||
}
|
||||
|
|
@ -902,8 +956,10 @@ void DisplayServerWeb::process_joypads() {
|
|||
for (int b = 0; b < s_btns_num; b++) {
|
||||
// Buttons 6 and 7 in the standard mapping need to be
|
||||
// axis to be handled as JoyAxis::TRIGGER by Godot.
|
||||
if (s_standard && (b == 6 || b == 7)) {
|
||||
input->joy_axis(idx, (JoyAxis)b, s_btns[b]);
|
||||
if (s_standard && (b == 6)) {
|
||||
input->joy_axis(idx, JoyAxis::TRIGGER_LEFT, s_btns[b]);
|
||||
} else if (s_standard && (b == 7)) {
|
||||
input->joy_axis(idx, JoyAxis::TRIGGER_RIGHT, s_btns[b]);
|
||||
} else {
|
||||
input->joy_button(idx, (JoyButton)b, s_btns[b]);
|
||||
}
|
||||
|
|
@ -1023,11 +1079,11 @@ void DisplayServerWeb::_dispatch_input_event(const Ref<InputEvent> &p_event) {
|
|||
}
|
||||
}
|
||||
|
||||
DisplayServer *DisplayServerWeb::create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error) {
|
||||
return memnew(DisplayServerWeb(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error));
|
||||
DisplayServer *DisplayServerWeb::create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
|
||||
return memnew(DisplayServerWeb(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error));
|
||||
}
|
||||
|
||||
DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error) {
|
||||
DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) {
|
||||
r_error = OK; // Always succeeds for now.
|
||||
|
||||
tts = GLOBAL_GET("audio/general/text_to_speech");
|
||||
|
|
@ -1131,6 +1187,8 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
|
|||
//case FEATURE_NATIVE_DIALOG:
|
||||
//case FEATURE_NATIVE_DIALOG_INPUT:
|
||||
//case FEATURE_NATIVE_DIALOG_FILE:
|
||||
//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
|
||||
//case FEATURE_NATIVE_DIALOG_FILE_MIME:
|
||||
//case FEATURE_NATIVE_ICON:
|
||||
//case FEATURE_WINDOW_TRANSPARENCY:
|
||||
//case FEATURE_KEEP_SCREEN_ON:
|
||||
|
|
|
|||
|
|
@ -106,6 +106,11 @@ private:
|
|||
bool tts = false;
|
||||
NativeMenu *native_menu = nullptr;
|
||||
|
||||
MouseMode mouse_mode_base = MOUSE_MODE_VISIBLE;
|
||||
MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
|
||||
bool mouse_mode_override_enabled = false;
|
||||
void _mouse_update_mode();
|
||||
|
||||
// utilities
|
||||
static void dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod, Key p_keycode);
|
||||
static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape);
|
||||
|
|
@ -148,7 +153,7 @@ private:
|
|||
void process_keys();
|
||||
|
||||
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_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error);
|
||||
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
|
||||
|
||||
static void _dispatch_input_event(const Ref<InputEvent> &p_event);
|
||||
|
||||
|
|
@ -184,6 +189,11 @@ public:
|
|||
// mouse
|
||||
virtual void mouse_set_mode(MouseMode p_mode) override;
|
||||
virtual MouseMode mouse_get_mode() const override;
|
||||
virtual void mouse_set_mode_override(MouseMode p_mode) override;
|
||||
virtual MouseMode mouse_get_mode_override() const override;
|
||||
virtual void mouse_set_mode_override_enabled(bool p_override_enabled) override;
|
||||
virtual bool mouse_is_mode_override_enabled() const override;
|
||||
|
||||
virtual Point2i mouse_get_position() const override;
|
||||
|
||||
// ime
|
||||
|
|
@ -209,6 +219,7 @@ public:
|
|||
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||
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 screen_set_keep_on(bool p_enable) 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;
|
||||
|
|
@ -265,6 +276,7 @@ public:
|
|||
|
||||
virtual bool can_any_window_draw() const override;
|
||||
|
||||
virtual void window_set_vsync_mode(VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override {}
|
||||
virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
|
||||
|
||||
// events
|
||||
|
|
@ -278,7 +290,7 @@ public:
|
|||
virtual void swap_buffers() override;
|
||||
|
||||
static void register_web_driver();
|
||||
DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error);
|
||||
DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
|
||||
~DisplayServerWeb();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -60,15 +60,15 @@
|
|||
</member>
|
||||
<member name="progressive_web_app/icon_144x144" type="String" setter="" getter="">
|
||||
File path to the smallest icon for this web application. If not defined, defaults to the project icon.
|
||||
[b]Note:[/b] If the icon is not 144x144, it will be automatically resized for the final build.
|
||||
[b]Note:[/b] If the icon is not 144×144, it will be automatically resized for the final build.
|
||||
</member>
|
||||
<member name="progressive_web_app/icon_180x180" type="String" setter="" getter="">
|
||||
File path to the small icon for this web application. If not defined, defaults to the project icon.
|
||||
[b]Note:[/b] If the icon is not 180x180, it will be automatically resized for the final build.
|
||||
[b]Note:[/b] If the icon is not 180×180, it will be automatically resized for the final build.
|
||||
</member>
|
||||
<member name="progressive_web_app/icon_512x512" type="String" setter="" getter="">
|
||||
File path to the smallest icon for this web application. If not defined, defaults to the project icon.
|
||||
[b]Note:[/b] If the icon is not 512x512, it will be automatically resized for the final build.
|
||||
File path to the largest icon for this web application. If not defined, defaults to the project icon.
|
||||
[b]Note:[/b] If the icon is not 512×512, it will be automatically resized for the final build.
|
||||
</member>
|
||||
<member name="progressive_web_app/offline_page" type="String" setter="" getter="">
|
||||
The page to display, should the server hosting the page not be available. This page is saved in the client's machine.
|
||||
|
|
@ -87,10 +87,10 @@
|
|||
If [code]false[/code], 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 an HTTPS website.
|
||||
</member>
|
||||
<member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
|
||||
If [code]true[/code], allows textures to be optimized for desktop through the S3TC algorithm.
|
||||
If [code]true[/code], allows textures to be optimized for desktop through the S3TC/BPTC algorithm.
|
||||
</member>
|
||||
<member name="vram_texture_compression/for_mobile" type="bool" setter="" getter="">
|
||||
If [code]true[/code] allows textures to be optimized for mobile through the ETC2 algorithm.
|
||||
If [code]true[/code] allows textures to be optimized for mobile through the ETC2/ASTC algorithm.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
|
|
|
|||
|
|
@ -30,14 +30,13 @@
|
|||
|
||||
#include "web_tools_editor_plugin.h"
|
||||
|
||||
#if defined(TOOLS_ENABLED) && defined(WEB_ENABLED)
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#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 "editor/export/project_zip_packer.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
|
|
@ -63,26 +62,10 @@ void WebToolsEditorPlugin::_download_zip() {
|
|||
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();
|
||||
|
||||
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.
|
||||
// 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", project_name_safe, datetime_safe));
|
||||
const String output_name = ProjectZIPPacker::get_project_zip_safe_name();
|
||||
const String output_path = String("/tmp").path_join(output_name);
|
||||
ProjectZIPPacker::pack_project_zip(output_path);
|
||||
|
||||
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);
|
||||
{
|
||||
Ref<FileAccess> f = FileAccess::open(output_path, FileAccess::READ);
|
||||
ERR_FAIL_COND_MSG(f.is_null(), "Unable to create ZIP file.");
|
||||
|
|
@ -95,65 +78,3 @@ void WebToolsEditorPlugin::_download_zip() {
|
|||
// 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 WebToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) {
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
|
||||
if (f.is_null()) {
|
||||
WARN_PRINT("Unable to open file for zipping: " + p_path);
|
||||
return;
|
||||
}
|
||||
Vector<uint8_t> data;
|
||||
uint64_t len = f->get_length();
|
||||
data.resize(len);
|
||||
f->get_buffer(data.ptrw(), len);
|
||||
|
||||
String path = p_path.replace_first(p_base_path, "");
|
||||
zipOpenNewFileInZip(p_zip,
|
||||
path.utf8().get_data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
Z_DEFLATED,
|
||||
Z_DEFAULT_COMPRESSION);
|
||||
zipWriteInFileInZip(p_zip, data.ptr(), data.size());
|
||||
zipCloseFileInZip(p_zip);
|
||||
}
|
||||
|
||||
void WebToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) {
|
||||
Ref<DirAccess> dir = DirAccess::open(p_path);
|
||||
if (dir.is_null()) {
|
||||
WARN_PRINT("Unable to open directory for zipping: " + p_path);
|
||||
return;
|
||||
}
|
||||
dir->list_dir_begin();
|
||||
String cur = dir->get_next();
|
||||
String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name();
|
||||
while (!cur.is_empty()) {
|
||||
String cs = p_path.path_join(cur);
|
||||
if (cur == "." || cur == ".." || cur == project_data_dir_name) {
|
||||
// Skip
|
||||
} else if (dir->current_is_dir()) {
|
||||
String path = cs.replace_first(p_base_path, "") + "/";
|
||||
zipOpenNewFileInZip(p_zip,
|
||||
path.utf8().get_data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
Z_DEFLATED,
|
||||
Z_DEFAULT_COMPRESSION);
|
||||
zipCloseFileInZip(p_zip);
|
||||
_zip_recursive(cs, p_base_path, p_zip);
|
||||
} else {
|
||||
_zip_file(cs, p_base_path, p_zip);
|
||||
}
|
||||
cur = dir->get_next();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED && WEB_ENABLED
|
||||
|
|
@ -31,8 +31,6 @@
|
|||
#ifndef WEB_TOOLS_EDITOR_PLUGIN_H
|
||||
#define WEB_TOOLS_EDITOR_PLUGIN_H
|
||||
|
||||
#if defined(TOOLS_ENABLED) && defined(WEB_ENABLED)
|
||||
|
||||
#include "core/io/zip_io.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
||||
|
|
@ -40,8 +38,6 @@ class WebToolsEditorPlugin : public EditorPlugin {
|
|||
GDCLASS(WebToolsEditorPlugin, EditorPlugin);
|
||||
|
||||
private:
|
||||
void _zip_file(String p_path, String p_base_path, zipFile p_zip);
|
||||
void _zip_recursive(String p_path, String p_base_path, zipFile p_zip);
|
||||
void _download_zip();
|
||||
|
||||
public:
|
||||
|
|
@ -57,6 +53,4 @@ public:
|
|||
static void initialize() {}
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED && WEB_ENABLED
|
||||
|
||||
#endif // WEB_TOOLS_EDITOR_PLUGIN_H
|
||||
|
|
@ -3,6 +3,8 @@ import os
|
|||
|
||||
from SCons.Util import WhereIs
|
||||
|
||||
from platform_methods import get_build_version
|
||||
|
||||
|
||||
def run_closure_compiler(target, source, env, for_signature):
|
||||
closure_bin = os.path.join(
|
||||
|
|
@ -21,22 +23,6 @@ def run_closure_compiler(target, source, env, for_signature):
|
|||
return " ".join(cmd)
|
||||
|
||||
|
||||
def get_build_version():
|
||||
import version
|
||||
|
||||
name = "custom_build"
|
||||
if os.getenv("BUILD_NAME") is not None:
|
||||
name = os.getenv("BUILD_NAME")
|
||||
v = "%d.%d" % (version.major, version.minor)
|
||||
if version.patch > 0:
|
||||
v += ".%d" % version.patch
|
||||
status = version.status
|
||||
if os.getenv("GODOT_VERSION_STATUS") is not None:
|
||||
status = str(os.getenv("GODOT_VERSION_STATUS"))
|
||||
v += ".%s.%s" % (status, name)
|
||||
return v
|
||||
|
||||
|
||||
def create_engine_file(env, target, source, externs, threads_enabled):
|
||||
if env["use_closure_compiler"]:
|
||||
return env.BuildJS(target, source, JSEXTERNS=externs)
|
||||
|
|
@ -44,22 +30,21 @@ def create_engine_file(env, target, source, externs, threads_enabled):
|
|||
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):
|
||||
def create_template_zip(env, js, wasm, side):
|
||||
binary_name = "godot.editor" if env.editor_build else "godot"
|
||||
zip_dir = env.Dir(env.GetTemplateZipPath())
|
||||
in_files = [
|
||||
js,
|
||||
wasm,
|
||||
"#platform/web/js/libs/audio.worklet.js",
|
||||
"#platform/web/js/libs/audio.position.worklet.js",
|
||||
]
|
||||
out_files = [
|
||||
zip_dir.File(binary_name + ".js"),
|
||||
zip_dir.File(binary_name + ".wasm"),
|
||||
zip_dir.File(binary_name + ".audio.worklet.js"),
|
||||
zip_dir.File(binary_name + ".audio.position.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).
|
||||
|
|
@ -74,19 +59,19 @@ def create_template_zip(env, js, wasm, worker, side):
|
|||
"offline.html",
|
||||
"godot.editor.js",
|
||||
"godot.editor.audio.worklet.js",
|
||||
"godot.editor.audio.position.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_VERSION___": get_build_version(False),
|
||||
"___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",
|
||||
"___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": "true",
|
||||
}
|
||||
html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict)
|
||||
in_files.append(html)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ void EditorHTTPServer::_send_response() {
|
|||
|
||||
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 cache_path = EditorPaths::get_singleton()->get_temp_dir().path_join("web");
|
||||
const String filepath = cache_path.path_join(req_file);
|
||||
|
||||
if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ void register_web_exporter_types() {
|
|||
}
|
||||
|
||||
void register_web_exporter() {
|
||||
#ifndef ANDROID_ENABLED
|
||||
// TODO: Move to editor_settings.cpp
|
||||
EDITOR_DEF("export/web/http_host", "localhost");
|
||||
EDITOR_DEF("export/web/http_port", 8060);
|
||||
EDITOR_DEF("export/web/use_tls", false);
|
||||
|
|
@ -49,7 +49,6 @@ void register_web_exporter() {
|
|||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1"));
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/tls_key", PROPERTY_HINT_GLOBAL_FILE, "*.key"));
|
||||
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/web/tls_certificate", PROPERTY_HINT_GLOBAL_FILE, "*.crt,*.pem"));
|
||||
#endif
|
||||
|
||||
Ref<EditorExportPlatformWeb> platform;
|
||||
platform.instantiate();
|
||||
|
|
|
|||
|
|
@ -41,10 +41,8 @@
|
|||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For mono and svg.
|
||||
#ifdef MODULE_SVG_ENABLED
|
||||
#include "modules/modules_enabled.gen.h" // For mono.
|
||||
#include "modules/svg/image_loader_svg.h"
|
||||
#endif
|
||||
|
||||
Error EditorExportPlatformWeb::_extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa) {
|
||||
Ref<FileAccess> io_fa;
|
||||
|
|
@ -130,15 +128,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_
|
|||
}
|
||||
}
|
||||
|
||||
void EditorExportPlatformWeb::_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) {
|
||||
void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
|
||||
// Engine.js config
|
||||
Dictionary config;
|
||||
Array libs;
|
||||
for (int i = 0; i < p_shared_objects.size(); i++) {
|
||||
libs.push_back(p_shared_objects[i].path.get_file());
|
||||
}
|
||||
Vector<String> flags;
|
||||
gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
|
||||
Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
|
||||
Array args;
|
||||
for (int i = 0; i < flags.size(); i++) {
|
||||
args.push_back(flags[i]);
|
||||
|
|
@ -170,6 +167,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;
|
||||
replaces["$GODOT_SPLASH_COLOR"] = "#" + Color(GLOBAL_GET("application/boot_splash/bg_color")).to_html(false);
|
||||
|
||||
LocalVector<String> godot_splash_classes;
|
||||
godot_splash_classes.push_back("show-image--" + String(GLOBAL_GET("application/boot_splash/show_image")));
|
||||
godot_splash_classes.push_back("fullsize--" + String(GLOBAL_GET("application/boot_splash/fullsize")));
|
||||
godot_splash_classes.push_back("use-filter--" + String(GLOBAL_GET("application/boot_splash/use_filter")));
|
||||
replaces["$GODOT_SPLASH_CLASSES"] = String(" ").join(godot_splash_classes);
|
||||
replaces["$GODOT_SPLASH"] = p_name + ".png";
|
||||
|
||||
if (p_preset->get("variant/thread_support")) {
|
||||
|
|
@ -188,9 +192,9 @@ Error EditorExportPlatformWeb::_add_manifest_icon(const String &p_path, const St
|
|||
|
||||
Ref<Image> icon;
|
||||
if (!p_icon.is_empty()) {
|
||||
icon.instantiate();
|
||||
const Error err = ImageLoader::load_image(p_icon, icon);
|
||||
if (err != OK) {
|
||||
Error err = OK;
|
||||
icon = _load_icon_or_splash_image(p_icon, &err);
|
||||
if (err != OK || icon.is_null() || icon->is_empty()) {
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not read file: \"%s\"."), p_icon));
|
||||
return err;
|
||||
}
|
||||
|
|
@ -240,8 +244,9 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
|
|||
cache_files.push_back(name + ".icon.png");
|
||||
cache_files.push_back(name + ".apple-touch-icon.png");
|
||||
}
|
||||
cache_files.push_back(name + ".worker.js");
|
||||
|
||||
cache_files.push_back(name + ".audio.worklet.js");
|
||||
cache_files.push_back(name + ".audio.position.worklet.js");
|
||||
replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
|
||||
|
||||
// Heavy files that are cached on demand.
|
||||
|
|
@ -333,9 +338,11 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
|
|||
void EditorExportPlatformWeb::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");
|
||||
r_features->push_back("bptc");
|
||||
}
|
||||
if (p_preset->get("vram_texture_compression/for_mobile")) {
|
||||
r_features->push_back("etc2");
|
||||
r_features->push_back("astc");
|
||||
}
|
||||
if (p_preset->get("variant/thread_support").operator bool()) {
|
||||
r_features->push_back("threads");
|
||||
|
|
@ -371,6 +378,16 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
|
|||
r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "progressive_web_app/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color()));
|
||||
}
|
||||
|
||||
bool EditorExportPlatformWeb::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
|
||||
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
|
||||
if (p_option == "custom_template/debug" ||
|
||||
p_option == "custom_template/release") {
|
||||
return advanced_options_enabled;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String EditorExportPlatformWeb::get_name() const {
|
||||
return "Web";
|
||||
}
|
||||
|
|
@ -449,7 +466,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo
|
|||
return list;
|
||||
}
|
||||
|
||||
Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
|
||||
Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
|
||||
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
|
||||
|
||||
const String custom_debug = p_preset->get("custom_template/debug");
|
||||
|
|
@ -743,7 +760,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
|
|||
return "";
|
||||
}
|
||||
|
||||
Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
|
||||
Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
|
||||
const uint16_t bind_port = EDITOR_GET("export/web/http_port");
|
||||
// Resolve host if needed.
|
||||
const String bind_host = EDITOR_GET("export/web/http_host");
|
||||
|
|
@ -816,7 +833,7 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
|
|||
}
|
||||
|
||||
Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_preset, int p_debug_flags) {
|
||||
const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("web");
|
||||
const String dest = EditorPaths::get_singleton()->get_temp_dir().path_join("web");
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
||||
if (!da->dir_exists(dest)) {
|
||||
Error err = da->make_dir_recursive(dest);
|
||||
|
|
@ -833,8 +850,8 @@ Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_
|
|||
DirAccess::remove_file_or_error(basepath + ".html");
|
||||
DirAccess::remove_file_or_error(basepath + ".offline.html");
|
||||
DirAccess::remove_file_or_error(basepath + ".js");
|
||||
DirAccess::remove_file_or_error(basepath + ".worker.js");
|
||||
DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
|
||||
DirAccess::remove_file_or_error(basepath + ".audio.position.worklet.js");
|
||||
DirAccess::remove_file_or_error(basepath + ".service.worker.js");
|
||||
DirAccess::remove_file_or_error(basepath + ".pck");
|
||||
DirAccess::remove_file_or_error(basepath + ".png");
|
||||
|
|
@ -887,7 +904,6 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() {
|
|||
if (EditorNode::get_singleton()) {
|
||||
server.instantiate();
|
||||
|
||||
#ifdef MODULE_SVG_ENABLED
|
||||
Ref<Image> img = memnew(Image);
|
||||
const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
|
||||
|
||||
|
|
@ -896,7 +912,6 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() {
|
|||
|
||||
ImageLoaderSVG::create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false);
|
||||
run_icon = ImageTexture::create_from_image(img);
|
||||
#endif
|
||||
|
||||
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
|
||||
if (theme.is_valid()) {
|
||||
|
|
|
|||
|
|
@ -77,20 +77,28 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
|
|||
}
|
||||
|
||||
Ref<Image> _get_project_icon() const {
|
||||
Error err = OK;
|
||||
Ref<Image> icon;
|
||||
icon.instantiate();
|
||||
const String icon_path = String(GLOBAL_GET("application/config/icon")).strip_edges();
|
||||
if (icon_path.is_empty() || ImageLoader::load_image(icon_path, icon) != OK) {
|
||||
if (!icon_path.is_empty()) {
|
||||
icon = _load_icon_or_splash_image(icon_path, &err);
|
||||
}
|
||||
if (icon_path.is_empty() || err != OK || icon.is_null() || icon->is_empty()) {
|
||||
return EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("DefaultProjectIcon"), EditorStringName(EditorIcons))->get_image();
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
Ref<Image> _get_project_splash() const {
|
||||
Error err = OK;
|
||||
Ref<Image> splash;
|
||||
splash.instantiate();
|
||||
const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges();
|
||||
if (splash_path.is_empty() || ImageLoader::load_image(splash_path, splash) != OK) {
|
||||
if (!splash_path.is_empty()) {
|
||||
splash = _load_icon_or_splash_image(splash_path, &err);
|
||||
}
|
||||
if (splash_path.is_empty() || err != OK || splash.is_null() || splash->is_empty()) {
|
||||
return Ref<Image>(memnew(Image(boot_splash_png)));
|
||||
}
|
||||
return splash;
|
||||
|
|
@ -98,7 +106,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
|
|||
|
||||
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
|
||||
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);
|
||||
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> 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);
|
||||
|
|
@ -112,6 +120,7 @@ public:
|
|||
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) const override;
|
||||
virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
|
||||
|
||||
virtual String get_name() const override;
|
||||
virtual String get_os_name() const override;
|
||||
|
|
@ -120,14 +129,14 @@ public:
|
|||
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) 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;
|
||||
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
|
||||
|
||||
virtual bool poll_export() override;
|
||||
virtual int get_options_count() const override;
|
||||
virtual String get_option_label(int p_index) const override;
|
||||
virtual String get_option_tooltip(int p_index) const override;
|
||||
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
|
||||
virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
|
||||
virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
|
||||
virtual Ref<Texture2D> get_run_icon() const override;
|
||||
|
||||
virtual void get_platform_features(List<String> *r_features) const override {
|
||||
|
|
|
|||
|
|
@ -51,10 +51,11 @@ extern void godot_audio_input_stop();
|
|||
extern int godot_audio_sample_stream_is_registered(const char *p_stream_object_id);
|
||||
extern void godot_audio_sample_register_stream(const char *p_stream_object_id, float *p_frames_buf, int p_frames_total, const char *p_loop_mode, int p_loop_begin, int p_loop_end);
|
||||
extern void godot_audio_sample_unregister_stream(const char *p_stream_object_id);
|
||||
extern void godot_audio_sample_start(const char *p_playback_object_id, const char *p_stream_object_id, int p_bus_index, float p_offset, float *p_volume_ptr);
|
||||
extern void godot_audio_sample_start(const char *p_playback_object_id, const char *p_stream_object_id, int p_bus_index, float p_offset, float p_pitch_scale, float *p_volume_ptr);
|
||||
extern void godot_audio_sample_stop(const char *p_playback_object_id);
|
||||
extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool p_pause);
|
||||
extern int godot_audio_sample_is_active(const char *p_playback_object_id);
|
||||
extern double godot_audio_get_sample_playback_position(const char *p_playback_object_id);
|
||||
extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale);
|
||||
extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size);
|
||||
extern void godot_audio_sample_set_finished_callback(void (*p_callback)(const char *));
|
||||
|
|
|
|||
51
engine/platform/web/godot_midi.h
Normal file
51
engine/platform/web/godot_midi.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**************************************************************************/
|
||||
/* godot_midi.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef GODOT_MIDI_H
|
||||
#define GODOT_MIDI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
extern int godot_js_webmidi_open_midi_inputs(
|
||||
void (*p_callback)(int p_size, const char **p_connected_input_names),
|
||||
void (*p_on_midi_message)(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len),
|
||||
const uint8_t *p_data_buffer,
|
||||
const int p_data_buffer_len);
|
||||
|
||||
extern void godot_js_webmidi_close_midi_inputs();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // GODOT_MIDI_H
|
||||
|
|
@ -266,11 +266,11 @@ Error HTTPClientWeb::poll() {
|
|||
return OK;
|
||||
}
|
||||
|
||||
HTTPClient *HTTPClientWeb::_create_func() {
|
||||
return memnew(HTTPClientWeb);
|
||||
HTTPClient *HTTPClientWeb::_create_func(bool p_notify_postinitialize) {
|
||||
return static_cast<HTTPClient *>(ClassDB::creator<HTTPClientWeb>(p_notify_postinitialize));
|
||||
}
|
||||
|
||||
HTTPClient *(*HTTPClient::_create)() = HTTPClientWeb::_create_func;
|
||||
HTTPClient *(*HTTPClient::_create)(bool p_notify_postinitialize) = HTTPClientWeb::_create_func;
|
||||
|
||||
HTTPClientWeb::HTTPClientWeb() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ private:
|
|||
static void _parse_headers(int p_len, const char **p_headers, void *p_ref);
|
||||
|
||||
public:
|
||||
static HTTPClient *_create_func();
|
||||
static HTTPClient *_create_func(bool p_notify_postinitialize);
|
||||
|
||||
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
|
||||
|
||||
|
|
|
|||
48
engine/platform/web/ip_web.cpp
Normal file
48
engine/platform/web/ip_web.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**************************************************************************/
|
||||
/* ip_web.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 "ip_web.h"
|
||||
|
||||
void IPWeb::_resolve_hostname(List<IPAddress> &r_addresses, const String &p_hostname, Type p_type) const {
|
||||
}
|
||||
|
||||
void IPWeb::get_local_interfaces(HashMap<String, Interface_Info> *r_interfaces) const {
|
||||
}
|
||||
|
||||
void IPWeb::make_default() {
|
||||
_create = _create_web;
|
||||
}
|
||||
|
||||
IP *IPWeb::_create_web() {
|
||||
return memnew(IPWeb);
|
||||
}
|
||||
|
||||
IPWeb::IPWeb() {
|
||||
}
|
||||
51
engine/platform/web/ip_web.h
Normal file
51
engine/platform/web/ip_web.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**************************************************************************/
|
||||
/* ip_web.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IP_WEB_H
|
||||
#define IP_WEB_H
|
||||
|
||||
#include "core/io/ip.h"
|
||||
|
||||
class IPWeb : public IP {
|
||||
GDCLASS(IPWeb, IP);
|
||||
|
||||
virtual void _resolve_hostname(List<IPAddress> &r_addresses, const String &p_hostname, Type p_type = TYPE_ANY) const override;
|
||||
|
||||
private:
|
||||
static IP *_create_web();
|
||||
|
||||
public:
|
||||
virtual void get_local_interfaces(HashMap<String, Interface_Info> *r_interfaces) const override;
|
||||
|
||||
static void make_default();
|
||||
IPWeb();
|
||||
};
|
||||
|
||||
#endif // IP_WEB_H
|
||||
|
|
@ -59,6 +59,8 @@ extern void godot_js_wrapper_object_unref(int p_id);
|
|||
extern int godot_js_wrapper_create_cb(void *p_ref, void (*p_callback)(void *p_ref, int p_arg_id, int p_argc));
|
||||
extern void godot_js_wrapper_object_set_cb_ret(int p_type, godot_js_wrapper_ex *p_val);
|
||||
extern int godot_js_wrapper_create_object(const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback);
|
||||
extern int godot_js_wrapper_object_is_buffer(int p_id);
|
||||
extern int godot_js_wrapper_object_transfer_buffer(int p_id, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len));
|
||||
};
|
||||
|
||||
class JavaScriptObjectImpl : public JavaScriptObject {
|
||||
|
|
@ -304,7 +306,7 @@ Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argc
|
|||
r_error.expected = 1;
|
||||
return Ref<JavaScriptObject>();
|
||||
}
|
||||
if (p_args[0]->get_type() != Variant::STRING) {
|
||||
if (!p_args[0]->is_string()) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
|
||||
r_error.argument = 0;
|
||||
r_error.expected = Variant::STRING;
|
||||
|
|
@ -366,6 +368,27 @@ Variant JavaScriptBridge::eval(const String &p_code, bool p_use_global_exec_cont
|
|||
}
|
||||
}
|
||||
|
||||
bool JavaScriptBridge::is_js_buffer(Ref<JavaScriptObject> p_js_obj) {
|
||||
Ref<JavaScriptObjectImpl> obj = p_js_obj;
|
||||
if (obj.is_null()) {
|
||||
return false;
|
||||
}
|
||||
return godot_js_wrapper_object_is_buffer(obj->_js_id);
|
||||
}
|
||||
|
||||
PackedByteArray JavaScriptBridge::js_buffer_to_packed_byte_array(Ref<JavaScriptObject> p_js_obj) {
|
||||
ERR_FAIL_COND_V_MSG(!is_js_buffer(p_js_obj), PackedByteArray(), "The JavaScript object is not a buffer.");
|
||||
Ref<JavaScriptObjectImpl> obj = p_js_obj;
|
||||
|
||||
PackedByteArray arr;
|
||||
VectorWriteProxy<uint8_t> arr_write;
|
||||
|
||||
godot_js_wrapper_object_transfer_buffer(obj->_js_id, &arr, &arr_write, resize_PackedByteArray_and_open_write);
|
||||
|
||||
arr_write = VectorWriteProxy<uint8_t>();
|
||||
return arr;
|
||||
}
|
||||
|
||||
#endif // JAVASCRIPT_EVAL_ENABLED
|
||||
|
||||
void JavaScriptBridge::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) {
|
||||
|
|
|
|||
|
|
@ -295,10 +295,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
'locateFile': function (path) {
|
||||
if (!path.startsWith('godot.')) {
|
||||
return path;
|
||||
} else if (path.endsWith('.worker.js')) {
|
||||
return `${loadPath}.worker.js`;
|
||||
} else if (path.endsWith('.audio.worklet.js')) {
|
||||
return `${loadPath}.audio.worklet.js`;
|
||||
} else if (path.endsWith('.audio.position.worklet.js')) {
|
||||
return `${loadPath}.audio.position.worklet.js`;
|
||||
} else if (path.endsWith('.js')) {
|
||||
return `${loadPath}.js`;
|
||||
} else if (path in gdext) {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,11 @@ const Engine = (function () {
|
|||
*/
|
||||
installServiceWorker: function () {
|
||||
if (this.config.serviceWorker && 'serviceWorker' in navigator) {
|
||||
return navigator.serviceWorker.register(this.config.serviceWorker);
|
||||
try {
|
||||
return navigator.serviceWorker.register(this.config.serviceWorker);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
69
engine/platform/web/js/libs/audio.position.worklet.js
Normal file
69
engine/platform/web/js/libs/audio.position.worklet.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**************************************************************************/
|
||||
/* godot.audio.position.worklet.js */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
const POST_THRESHOLD_S = 0.1;
|
||||
|
||||
class GodotPositionReportingProcessor extends AudioWorkletProcessor {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.lastPostTime = currentTime;
|
||||
this.position = 0;
|
||||
this.ended = false;
|
||||
|
||||
this.port.onmessage = (event) => {
|
||||
if (event?.data?.type === 'ended') {
|
||||
this.ended = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
process(inputs, _outputs, _parameters) {
|
||||
if (this.ended) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputs.length > 0) {
|
||||
const input = inputs[0];
|
||||
if (input.length > 0) {
|
||||
this.position += input[0].length;
|
||||
}
|
||||
}
|
||||
|
||||
// Posting messages is expensive. Let's limit the number of posts.
|
||||
if (currentTime - this.lastPostTime > POST_THRESHOLD_S) {
|
||||
this.lastPostTime = currentTime;
|
||||
this.port.postMessage({ type: 'position', data: this.position });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('godot-position-reporting-processor', GodotPositionReportingProcessor);
|
||||
|
|
@ -77,7 +77,7 @@ class Sample {
|
|||
* Creates a `Sample` based on the params. Will register it to the
|
||||
* `GodotAudio.samples` registry.
|
||||
* @param {SampleParams} params Base params
|
||||
* @param {SampleOptions} [options={{}}] Optional params
|
||||
* @param {SampleOptions | undefined} options Optional params.
|
||||
* @returns {Sample}
|
||||
*/
|
||||
static create(params, options = {}) {
|
||||
|
|
@ -98,7 +98,7 @@ class Sample {
|
|||
/**
|
||||
* `Sample` constructor.
|
||||
* @param {SampleParams} params Base params
|
||||
* @param {SampleOptions} [options={{}}] Optional params
|
||||
* @param {SampleOptions | undefined} options Optional params.
|
||||
*/
|
||||
constructor(params, options = {}) {
|
||||
/** @type {string} */
|
||||
|
|
@ -328,8 +328,10 @@ class SampleNodeBus {
|
|||
* offset?: number
|
||||
* playbackRate?: number
|
||||
* startTime?: number
|
||||
* pitchScale?: number
|
||||
* loopMode?: LoopMode
|
||||
* volume?: Float32Array
|
||||
* start?: boolean
|
||||
* }} SampleNodeOptions
|
||||
*/
|
||||
|
||||
|
|
@ -391,7 +393,7 @@ class SampleNode {
|
|||
* Creates a `SampleNode` based on the params. Will register the `SampleNode` to
|
||||
* the `GodotAudio.sampleNodes` regisery.
|
||||
* @param {SampleNodeParams} params Base params.
|
||||
* @param {SampleNodeOptions} options Optional params.
|
||||
* @param {SampleNodeOptions | undefined} options Optional params.
|
||||
* @returns {SampleNode}
|
||||
*/
|
||||
static create(params, options = {}) {
|
||||
|
|
@ -411,7 +413,7 @@ class SampleNode {
|
|||
|
||||
/**
|
||||
* @param {SampleNodeParams} params Base params
|
||||
* @param {SampleNodeOptions} [options={{}}] Optional params
|
||||
* @param {SampleNodeOptions | undefined} options Optional params.
|
||||
*/
|
||||
constructor(params, options = {}) {
|
||||
/** @type {string} */
|
||||
|
|
@ -421,9 +423,15 @@ class SampleNode {
|
|||
/** @type {number} */
|
||||
this.offset = options.offset ?? 0;
|
||||
/** @type {number} */
|
||||
this._playbackPosition = options.offset;
|
||||
/** @type {number} */
|
||||
this.startTime = options.startTime ?? 0;
|
||||
/** @type {boolean} */
|
||||
this.isPaused = false;
|
||||
/** @type {boolean} */
|
||||
this.isStarted = false;
|
||||
/** @type {boolean} */
|
||||
this.isCanceled = false;
|
||||
/** @type {number} */
|
||||
this.pauseTime = 0;
|
||||
/** @type {number} */
|
||||
|
|
@ -431,7 +439,7 @@ class SampleNode {
|
|||
/** @type {LoopMode} */
|
||||
this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
|
||||
/** @type {number} */
|
||||
this._pitchScale = 1;
|
||||
this._pitchScale = options.pitchScale ?? 1;
|
||||
/** @type {number} */
|
||||
this._sourceStartTime = 0;
|
||||
/** @type {Map<Bus, SampleNodeBus>} */
|
||||
|
|
@ -440,6 +448,8 @@ class SampleNode {
|
|||
this._source = GodotAudio.ctx.createBufferSource();
|
||||
|
||||
this._onended = null;
|
||||
/** @type {AudioWorkletNode | null} */
|
||||
this._positionWorklet = null;
|
||||
|
||||
this.setPlaybackRate(options.playbackRate ?? 44100);
|
||||
this._source.buffer = this.getSample().getAudioBuffer();
|
||||
|
|
@ -449,6 +459,12 @@ class SampleNode {
|
|||
const bus = GodotAudio.Bus.getBus(params.busIndex);
|
||||
const sampleNodeBus = this.getSampleNodeBus(bus);
|
||||
sampleNodeBus.setVolume(options.volume);
|
||||
|
||||
this.connectPositionWorklet(options.start).catch((err) => {
|
||||
const newErr = new Error('Failed to create PositionWorklet.');
|
||||
newErr.cause = err;
|
||||
GodotRuntime.error(newErr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -459,6 +475,14 @@ class SampleNode {
|
|||
return this._playbackRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the playback position.
|
||||
* @returns {number}
|
||||
*/
|
||||
getPlaybackPosition() {
|
||||
return this._playbackPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback rate.
|
||||
* @param {number} val Value to set.
|
||||
|
|
@ -508,8 +532,12 @@ class SampleNode {
|
|||
* @returns {void}
|
||||
*/
|
||||
start() {
|
||||
if (this.isStarted) {
|
||||
return;
|
||||
}
|
||||
this._resetSourceStartTime();
|
||||
this._source.start(this.startTime, this.offset);
|
||||
this.isStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -584,18 +612,60 @@ class SampleNode {
|
|||
return this._sampleNodeBuses.get(bus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up and connects the source to the GodotPositionReportingProcessor
|
||||
* If the worklet module is not loaded in, it will be added
|
||||
*/
|
||||
async connectPositionWorklet(start) {
|
||||
await GodotAudio.audioPositionWorkletPromise;
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
this._source.connect(this.getPositionWorklet());
|
||||
if (start) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a AudioWorkletProcessor
|
||||
* @returns {AudioWorkletNode}
|
||||
*/
|
||||
getPositionWorklet() {
|
||||
if (this._positionWorklet != null) {
|
||||
return this._positionWorklet;
|
||||
}
|
||||
this._positionWorklet = new AudioWorkletNode(
|
||||
GodotAudio.ctx,
|
||||
'godot-position-reporting-processor'
|
||||
);
|
||||
this._positionWorklet.port.onmessage = (event) => {
|
||||
switch (event.data['type']) {
|
||||
case 'position':
|
||||
this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
return this._positionWorklet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the `SampleNode`.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
this.isCanceled = true;
|
||||
this.isPaused = false;
|
||||
this.pauseTime = 0;
|
||||
|
||||
if (this._source != null) {
|
||||
this._source.removeEventListener('ended', this._onended);
|
||||
this._onended = null;
|
||||
this._source.stop();
|
||||
if (this.isStarted) {
|
||||
this._source.stop();
|
||||
}
|
||||
this._source.disconnect();
|
||||
this._source = null;
|
||||
}
|
||||
|
|
@ -605,6 +675,13 @@ class SampleNode {
|
|||
}
|
||||
this._sampleNodeBuses.clear();
|
||||
|
||||
if (this._positionWorklet) {
|
||||
this._positionWorklet.disconnect();
|
||||
this._positionWorklet.port.onmessage = null;
|
||||
this._positionWorklet.port.postMessage({ type: 'ended' });
|
||||
this._positionWorklet = null;
|
||||
}
|
||||
|
||||
GodotAudio.SampleNode.delete(this.id);
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +722,12 @@ class SampleNode {
|
|||
const pauseTime = this.isPaused
|
||||
? this.pauseTime
|
||||
: 0;
|
||||
if (this._positionWorklet != null) {
|
||||
this._positionWorklet.port.postMessage({ type: 'clear' });
|
||||
this._source.connect(this._positionWorklet);
|
||||
}
|
||||
this._source.start(this.startTime, this.offset + pauseTime);
|
||||
this.isStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -653,6 +735,9 @@ class SampleNode {
|
|||
* @returns {void}
|
||||
*/
|
||||
_pause() {
|
||||
if (!this.isStarted) {
|
||||
return;
|
||||
}
|
||||
this.isPaused = true;
|
||||
this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
|
||||
this._source.stop();
|
||||
|
|
@ -780,7 +865,10 @@ class Bus {
|
|||
* @returns {void}
|
||||
*/
|
||||
static move(fromIndex, toIndex) {
|
||||
const movedBus = GodotAudio.Bus.getBus(fromIndex);
|
||||
const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
|
||||
if (movedBus == null) {
|
||||
return;
|
||||
}
|
||||
const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
|
||||
// Inserts at index.
|
||||
buses.splice(toIndex - 1, 0, movedBus);
|
||||
|
|
@ -1108,6 +1196,9 @@ const _GodotAudio = {
|
|||
driver: null,
|
||||
interval: 0,
|
||||
|
||||
/** @type {Promise} */
|
||||
audioPositionWorkletPromise: null,
|
||||
|
||||
/**
|
||||
* Converts linear volume to Db.
|
||||
* @param {number} linear Linear value to convert.
|
||||
|
|
@ -1174,6 +1265,10 @@ const _GodotAudio = {
|
|||
onlatencyupdate(computed_latency);
|
||||
}, 1000);
|
||||
GodotOS.atexit(GodotAudio.close_async);
|
||||
|
||||
const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
|
||||
GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path);
|
||||
|
||||
return ctx.destination.channelCount;
|
||||
},
|
||||
|
||||
|
|
@ -1252,7 +1347,7 @@ const _GodotAudio = {
|
|||
* @param {string} playbackObjectId The unique id of the sample playback
|
||||
* @param {string} streamObjectId The unique id of the stream
|
||||
* @param {number} busIndex Index of the bus currently binded to the sample playback
|
||||
* @param {SampleNodeOptions} startOptions Optional params
|
||||
* @param {SampleNodeOptions | undefined} startOptions Optional params.
|
||||
* @returns {void}
|
||||
*/
|
||||
start_sample: function (
|
||||
|
|
@ -1262,7 +1357,7 @@ const _GodotAudio = {
|
|||
startOptions
|
||||
) {
|
||||
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
|
||||
const sampleNode = GodotAudio.SampleNode.create(
|
||||
GodotAudio.SampleNode.create(
|
||||
{
|
||||
busIndex,
|
||||
id: playbackObjectId,
|
||||
|
|
@ -1270,7 +1365,6 @@ const _GodotAudio = {
|
|||
},
|
||||
startOptions
|
||||
);
|
||||
sampleNode.start();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -1337,7 +1431,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
remove_sample_bus: function (index) {
|
||||
const bus = GodotAudio.Bus.getBus(index);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(index);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.clear();
|
||||
},
|
||||
|
||||
|
|
@ -1367,8 +1464,17 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_send: function (busIndex, sendIndex) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
bus.setSend(GodotAudio.Bus.getBus(sendIndex));
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
// Cannot send from an invalid bus.
|
||||
return;
|
||||
}
|
||||
let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
|
||||
if (targetBus == null) {
|
||||
// Send to master.
|
||||
targetBus = GodotAudio.Bus.getBus(0);
|
||||
}
|
||||
bus.setSend(targetBus);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -1378,7 +1484,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_volume_db: function (busIndex, volumeDb) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.setVolumeDb(volumeDb);
|
||||
},
|
||||
|
||||
|
|
@ -1389,7 +1498,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_solo: function (busIndex, enable) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.solo(enable);
|
||||
},
|
||||
|
||||
|
|
@ -1400,7 +1512,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_mute: function (busIndex, enable) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.mute(enable);
|
||||
},
|
||||
},
|
||||
|
|
@ -1562,13 +1677,14 @@ const _GodotAudio = {
|
|||
},
|
||||
|
||||
godot_audio_sample_start__proxy: 'sync',
|
||||
godot_audio_sample_start__sig: 'viiiii',
|
||||
godot_audio_sample_start__sig: 'viiiifi',
|
||||
/**
|
||||
* Starts a sample.
|
||||
* @param {number} playbackObjectIdStrPtr Playback object id pointer
|
||||
* @param {number} streamObjectIdStrPtr Stream object id pointer
|
||||
* @param {number} busIndex Bus index
|
||||
* @param {number} offset Sample offset
|
||||
* @param {number} pitchScale Pitch scale
|
||||
* @param {number} volumePtr Volume pointer
|
||||
* @returns {void}
|
||||
*/
|
||||
|
|
@ -1577,6 +1693,7 @@ const _GodotAudio = {
|
|||
streamObjectIdStrPtr,
|
||||
busIndex,
|
||||
offset,
|
||||
pitchScale,
|
||||
volumePtr
|
||||
) {
|
||||
/** @type {string} */
|
||||
|
|
@ -1585,11 +1702,13 @@ const _GodotAudio = {
|
|||
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
|
||||
/** @type {Float32Array} */
|
||||
const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8);
|
||||
/** @type {SampleNodeConstructorOptions} */
|
||||
/** @type {SampleNodeOptions} */
|
||||
const startOptions = {
|
||||
offset,
|
||||
volume,
|
||||
playbackRate: 1,
|
||||
pitchScale,
|
||||
start: true,
|
||||
};
|
||||
GodotAudio.start_sample(
|
||||
playbackObjectId,
|
||||
|
|
@ -1635,6 +1754,22 @@ const _GodotAudio = {
|
|||
return Number(GodotAudio.sampleNodes.has(playbackObjectId));
|
||||
},
|
||||
|
||||
godot_audio_get_sample_playback_position__proxy: 'sync',
|
||||
godot_audio_get_sample_playback_position__sig: 'di',
|
||||
/**
|
||||
* Returns the position of the playback position.
|
||||
* @param {number} playbackObjectIdStrPtr Playback object id pointer
|
||||
* @returns {number}
|
||||
*/
|
||||
godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
|
||||
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
|
||||
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
|
||||
if (sampleNode == null) {
|
||||
return 0;
|
||||
}
|
||||
return sampleNode.getPlaybackPosition();
|
||||
},
|
||||
|
||||
godot_audio_sample_update_pitch_scale__proxy: 'sync',
|
||||
godot_audio_sample_update_pitch_scale__sig: 'vii',
|
||||
/**
|
||||
|
|
@ -2029,7 +2164,7 @@ autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
|
|||
mergeInto(LibraryManager.library, GodotAudioWorklet);
|
||||
|
||||
/*
|
||||
* The ScriptProcessorNode API, used when threads are disabled.
|
||||
* The ScriptProcessorNode API, used as a fallback if AudioWorklet is not available.
|
||||
*/
|
||||
const GodotAudioScript = {
|
||||
$GodotAudioScript__deps: ['$GodotAudio'],
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ const GodotFetch = {
|
|||
});
|
||||
obj.status = response.status;
|
||||
obj.response = response;
|
||||
obj.reader = response.body.getReader();
|
||||
// `body` can be null per spec (for example, in cases where the request method is HEAD).
|
||||
// As of the time of writing, Chromium (127.0.6533.72) does not follow the spec but Firefox (131.0.3) does.
|
||||
// See godotengine/godot#76825 for more information.
|
||||
// See Chromium revert (of the change to follow the spec):
|
||||
// https://chromium.googlesource.com/chromium/src/+/135354b7bdb554cd03c913af7c90aceead03c4d4
|
||||
obj.reader = response.body?.getReader();
|
||||
obj.chunked = chunked;
|
||||
},
|
||||
|
||||
|
|
@ -121,6 +126,10 @@ const GodotFetch = {
|
|||
}
|
||||
obj.reading = true;
|
||||
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
|
||||
} else if (obj.reader == null && obj.response.body == null) {
|
||||
// Emulate a stream closure to maintain the request lifecycle.
|
||||
obj.reading = true;
|
||||
GodotFetch.onread(id, { value: undefined, done: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -159,7 +168,10 @@ const GodotFetch = {
|
|||
if (!obj.response) {
|
||||
return 0;
|
||||
}
|
||||
if (obj.reader) {
|
||||
// If the reader is nullish, but there is no body, and the request is not marked as done,
|
||||
// the same status should be returned as though the request is currently being read
|
||||
// so that the proper lifecycle closure can be handled in `read()`.
|
||||
if (obj.reader || (obj.response.body == null && !obj.done)) {
|
||||
return 1;
|
||||
}
|
||||
if (obj.done) {
|
||||
|
|
|
|||
|
|
@ -38,41 +38,57 @@ const GodotIME = {
|
|||
$GodotIME: {
|
||||
ime: null,
|
||||
active: false,
|
||||
focusTimerIntervalId: -1,
|
||||
|
||||
getModifiers: function (evt) {
|
||||
return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);
|
||||
},
|
||||
|
||||
ime_active: function (active) {
|
||||
function focus_timer() {
|
||||
GodotIME.active = true;
|
||||
function clearFocusTimerInterval() {
|
||||
clearInterval(GodotIME.focusTimerIntervalId);
|
||||
GodotIME.focusTimerIntervalId = -1;
|
||||
}
|
||||
|
||||
function focusTimer() {
|
||||
if (GodotIME.ime == null) {
|
||||
clearFocusTimerInterval();
|
||||
return;
|
||||
}
|
||||
GodotIME.ime.focus();
|
||||
}
|
||||
|
||||
if (GodotIME.ime) {
|
||||
if (active) {
|
||||
GodotIME.ime.style.display = 'block';
|
||||
setInterval(focus_timer, 100);
|
||||
} else {
|
||||
GodotIME.ime.style.display = 'none';
|
||||
GodotConfig.canvas.focus();
|
||||
GodotIME.active = false;
|
||||
}
|
||||
if (GodotIME.focusTimerIntervalId > -1) {
|
||||
clearFocusTimerInterval();
|
||||
}
|
||||
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GodotIME.active = active;
|
||||
if (active) {
|
||||
GodotIME.ime.style.display = 'block';
|
||||
GodotIME.focusTimerIntervalId = setInterval(focusTimer, 100);
|
||||
} else {
|
||||
GodotIME.ime.style.display = 'none';
|
||||
GodotConfig.canvas.focus();
|
||||
}
|
||||
},
|
||||
|
||||
ime_position: function (x, y) {
|
||||
if (GodotIME.ime) {
|
||||
const canvas = GodotConfig.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const rw = canvas.width / rect.width;
|
||||
const rh = canvas.height / rect.height;
|
||||
const clx = (x / rw) + rect.x;
|
||||
const cly = (y / rh) + rect.y;
|
||||
|
||||
GodotIME.ime.style.left = `${clx}px`;
|
||||
GodotIME.ime.style.top = `${cly}px`;
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
const canvas = GodotConfig.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const rw = canvas.width / rect.width;
|
||||
const rh = canvas.height / rect.height;
|
||||
const clx = (x / rw) + rect.x;
|
||||
const cly = (y / rh) + rect.y;
|
||||
|
||||
GodotIME.ime.style.left = `${clx}px`;
|
||||
GodotIME.ime.style.top = `${cly}px`;
|
||||
},
|
||||
|
||||
init: function (ime_cb, key_cb, code, key) {
|
||||
|
|
@ -84,20 +100,27 @@ const GodotIME = {
|
|||
evt.preventDefault();
|
||||
}
|
||||
function ime_event_cb(event) {
|
||||
if (GodotIME.ime) {
|
||||
if (event.type === 'compositionstart') {
|
||||
ime_cb(0, null);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
} else if (event.type === 'compositionupdate') {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(1, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
} else if (event.type === 'compositionend') {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(2, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
}
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
switch (event.type) {
|
||||
case 'compositionstart':
|
||||
ime_cb(0, null);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
break;
|
||||
case 'compositionupdate': {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(1, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
} break;
|
||||
case 'compositionend': {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(2, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
} break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +156,15 @@ const GodotIME = {
|
|||
},
|
||||
|
||||
clear: function () {
|
||||
if (GodotIME.ime) {
|
||||
GodotIME.ime.remove();
|
||||
GodotIME.ime = null;
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
if (GodotIME.focusTimerIntervalId > -1) {
|
||||
clearInterval(GodotIME.focusTimerIntervalId);
|
||||
GodotIME.focusTimerIntervalId = -1;
|
||||
}
|
||||
GodotIME.ime.remove();
|
||||
GodotIME.ime = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ const GodotJSWrapper = {
|
|||
GodotRuntime.setHeapValue(p_exchange, id, 'i64');
|
||||
return 24; // OBJECT
|
||||
},
|
||||
|
||||
isBuffer: function (obj) {
|
||||
return obj instanceof ArrayBuffer || ArrayBuffer.isView(obj);
|
||||
},
|
||||
},
|
||||
|
||||
godot_js_wrapper_interface_get__proxy: 'sync',
|
||||
|
|
@ -303,6 +307,34 @@ const GodotJSWrapper = {
|
|||
return -1;
|
||||
}
|
||||
},
|
||||
|
||||
godot_js_wrapper_object_is_buffer__proxy: 'sync',
|
||||
godot_js_wrapper_object_is_buffer__sig: 'ii',
|
||||
godot_js_wrapper_object_is_buffer: function (p_id) {
|
||||
const obj = GodotJSWrapper.get_proxied_value(p_id);
|
||||
return GodotJSWrapper.isBuffer(obj)
|
||||
? 1
|
||||
: 0;
|
||||
},
|
||||
|
||||
godot_js_wrapper_object_transfer_buffer__proxy: 'sync',
|
||||
godot_js_wrapper_object_transfer_buffer__sig: 'viiii',
|
||||
godot_js_wrapper_object_transfer_buffer: function (p_id, p_byte_arr, p_byte_arr_write, p_callback) {
|
||||
let obj = GodotJSWrapper.get_proxied_value(p_id);
|
||||
if (!GodotJSWrapper.isBuffer(obj)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(obj) && !(obj instanceof Uint8Array)) {
|
||||
obj = new Uint8Array(obj.buffer);
|
||||
} else if (obj instanceof ArrayBuffer) {
|
||||
obj = new Uint8Array(obj);
|
||||
}
|
||||
|
||||
const resizePackedByteArrayAndOpenWrite = GodotRuntime.get_func(p_callback);
|
||||
const bytesPtr = resizePackedByteArrayAndOpenWrite(p_byte_arr, p_byte_arr_write, obj.length);
|
||||
HEAPU8.set(obj, bytesPtr);
|
||||
},
|
||||
};
|
||||
|
||||
autoAddDeps(GodotJSWrapper, '$GodotJSWrapper');
|
||||
|
|
|
|||
|
|
@ -441,8 +441,12 @@ const GodotPWA = {
|
|||
godot_js_pwa_cb__sig: 'vi',
|
||||
godot_js_pwa_cb: function (p_update_cb) {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const cb = GodotRuntime.get_func(p_update_cb);
|
||||
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
|
||||
try {
|
||||
const cb = GodotRuntime.get_func(p_update_cb);
|
||||
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
|
||||
} catch (e) {
|
||||
GodotRuntime.error('Failed to assign PWA callback', e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -450,12 +454,17 @@ const GodotPWA = {
|
|||
godot_js_pwa_update__sig: 'i',
|
||||
godot_js_pwa_update: function () {
|
||||
if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {
|
||||
navigator.serviceWorker.getRegistration().then(function (reg) {
|
||||
if (!reg || !reg.waiting) {
|
||||
return;
|
||||
}
|
||||
reg.waiting.postMessage('update');
|
||||
});
|
||||
try {
|
||||
navigator.serviceWorker.getRegistration().then(function (reg) {
|
||||
if (!reg || !reg.waiting) {
|
||||
return;
|
||||
}
|
||||
reg.waiting.postMessage('update');
|
||||
});
|
||||
} catch (e) {
|
||||
GodotRuntime.error(e);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
|
|
|||
94
engine/platform/web/js/libs/library_godot_webmidi.js
Normal file
94
engine/platform/web/js/libs/library_godot_webmidi.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**************************************************************************/
|
||||
/* library_godot_webmidi.js */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
const GodotWebMidi = {
|
||||
|
||||
$GodotWebMidi__deps: ['$GodotRuntime'],
|
||||
$GodotWebMidi: {
|
||||
abortControllers: [],
|
||||
isListening: false,
|
||||
},
|
||||
|
||||
godot_js_webmidi_open_midi_inputs__deps: ['$GodotWebMidi'],
|
||||
godot_js_webmidi_open_midi_inputs__proxy: 'sync',
|
||||
godot_js_webmidi_open_midi_inputs__sig: 'iiii',
|
||||
godot_js_webmidi_open_midi_inputs: function (pSetInputNamesCb, pOnMidiMessageCb, pDataBuffer, dataBufferLen) {
|
||||
if (GodotWebMidi.is_listening) {
|
||||
return 0; // OK
|
||||
}
|
||||
if (!navigator.requestMIDIAccess) {
|
||||
return 2; // ERR_UNAVAILABLE
|
||||
}
|
||||
const setInputNamesCb = GodotRuntime.get_func(pSetInputNamesCb);
|
||||
const onMidiMessageCb = GodotRuntime.get_func(pOnMidiMessageCb);
|
||||
|
||||
GodotWebMidi.isListening = true;
|
||||
navigator.requestMIDIAccess().then((midi) => {
|
||||
const inputs = [...midi.inputs.values()];
|
||||
const inputNames = inputs.map((input) => input.name);
|
||||
|
||||
const c_ptr = GodotRuntime.allocStringArray(inputNames);
|
||||
setInputNamesCb(inputNames.length, c_ptr);
|
||||
GodotRuntime.freeStringArray(c_ptr, inputNames.length);
|
||||
|
||||
inputs.forEach((input, i) => {
|
||||
const abortController = new AbortController();
|
||||
GodotWebMidi.abortControllers.push(abortController);
|
||||
input.addEventListener('midimessage', (event) => {
|
||||
const status = event.data[0];
|
||||
const data = event.data.slice(1);
|
||||
const size = data.length;
|
||||
|
||||
if (size > dataBufferLen) {
|
||||
throw new Error(`data too big ${size} > ${dataBufferLen}`);
|
||||
}
|
||||
HEAPU8.set(data, pDataBuffer);
|
||||
|
||||
onMidiMessageCb(i, status, pDataBuffer, data.length);
|
||||
}, { signal: abortController.signal });
|
||||
});
|
||||
});
|
||||
|
||||
return 0; // OK
|
||||
},
|
||||
|
||||
godot_js_webmidi_close_midi_inputs__deps: ['$GodotWebMidi'],
|
||||
godot_js_webmidi_close_midi_inputs__proxy: 'sync',
|
||||
godot_js_webmidi_close_midi_inputs__sig: 'v',
|
||||
godot_js_webmidi_close_midi_inputs: function () {
|
||||
for (const abortController of GodotWebMidi.abortControllers) {
|
||||
abortController.abort();
|
||||
}
|
||||
GodotWebMidi.abortControllers = [];
|
||||
GodotWebMidi.isListening = false;
|
||||
},
|
||||
};
|
||||
|
||||
mergeInto(LibraryManager.library, GodotWebMidi);
|
||||
39
engine/platform/web/net_socket_web.cpp
Normal file
39
engine/platform/web/net_socket_web.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* net_socket_web.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 "net_socket_web.h"
|
||||
|
||||
NetSocket *NetSocketWeb::_create_func() {
|
||||
return memnew(NetSocketWeb);
|
||||
}
|
||||
|
||||
void NetSocketWeb::make_default() {
|
||||
_create = _create_func;
|
||||
}
|
||||
72
engine/platform/web/net_socket_web.h
Normal file
72
engine/platform/web/net_socket_web.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**************************************************************************/
|
||||
/* net_socket_web.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef NET_SOCKET_WEB_H
|
||||
#define NET_SOCKET_WEB_H
|
||||
|
||||
#include "core/io/net_socket.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
class NetSocketWeb : public NetSocket {
|
||||
protected:
|
||||
static NetSocket *_create_func();
|
||||
|
||||
public:
|
||||
static void make_default();
|
||||
|
||||
virtual Error open(Type p_sock_type, IP::Type &ip_type) override { return ERR_UNAVAILABLE; }
|
||||
virtual void close() override {}
|
||||
virtual Error bind(IPAddress p_addr, uint16_t p_port) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error listen(int p_max_pending) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error poll(PollType p_type, int timeout) const override { return ERR_UNAVAILABLE; }
|
||||
virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override { return ERR_UNAVAILABLE; }
|
||||
virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override { return Ref<NetSocket>(); }
|
||||
|
||||
virtual bool is_open() const override { return false; }
|
||||
virtual int get_available_bytes() const override { return -1; }
|
||||
virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override { return ERR_UNAVAILABLE; }
|
||||
|
||||
virtual Error set_broadcasting_enabled(bool p_enabled) override { return ERR_UNAVAILABLE; }
|
||||
virtual void set_blocking_enabled(bool p_enabled) override {}
|
||||
virtual void set_ipv6_only_enabled(bool p_enabled) override {}
|
||||
virtual void set_tcp_no_delay_enabled(bool p_enabled) override {}
|
||||
virtual void set_reuse_address_enabled(bool p_enabled) override {}
|
||||
virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override { return ERR_UNAVAILABLE; }
|
||||
virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override { return ERR_UNAVAILABLE; }
|
||||
|
||||
NetSocketWeb() {}
|
||||
};
|
||||
|
||||
#endif // NET_SOCKET_WEB_H
|
||||
|
|
@ -33,6 +33,8 @@
|
|||
#include "api/javascript_bridge_singleton.h"
|
||||
#include "display_server_web.h"
|
||||
#include "godot_js.h"
|
||||
#include "ip_web.h"
|
||||
#include "net_socket_web.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
|
|
@ -53,6 +55,8 @@ void OS_Web::alert(const String &p_alert, const String &p_title) {
|
|||
// Lifecycle
|
||||
void OS_Web::initialize() {
|
||||
OS_Unix::initialize_core();
|
||||
IPWeb::make_default();
|
||||
NetSocketWeb::make_default();
|
||||
DisplayServerWeb::register_web_driver();
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +109,7 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
|
|||
return create_process(p_path, p_arguments);
|
||||
}
|
||||
|
||||
Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
|
||||
Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
|
||||
ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
|
||||
}
|
||||
|
||||
|
|
@ -178,23 +182,9 @@ void OS_Web::vibrate_handheld(int p_duration_ms, float p_amplitude) {
|
|||
godot_js_input_vibrate_handheld(p_duration_ms);
|
||||
}
|
||||
|
||||
String OS_Web::get_user_data_dir() const {
|
||||
String OS_Web::get_user_data_dir(const String &p_user_dir) const {
|
||||
String userfs = "/userfs";
|
||||
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
|
||||
if (!appname.is_empty()) {
|
||||
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
|
||||
if (use_custom_dir) {
|
||||
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
|
||||
if (custom_dir.is_empty()) {
|
||||
custom_dir = appname;
|
||||
}
|
||||
return userfs.path_join(custom_dir).replace("\\", "/");
|
||||
} else {
|
||||
return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/");
|
||||
}
|
||||
}
|
||||
|
||||
return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
|
||||
return userfs.path_join(p_user_dir).replace("\\", "/");
|
||||
}
|
||||
|
||||
String OS_Web::get_cache_path() const {
|
||||
|
|
@ -224,6 +214,18 @@ void OS_Web::file_access_close_callback(const String &p_file, int p_flags) {
|
|||
}
|
||||
}
|
||||
|
||||
void OS_Web::dir_access_remove_callback(const String &p_file) {
|
||||
OS_Web *os = OS_Web::get_singleton();
|
||||
bool is_file_persistent = p_file.begins_with("/userfs");
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Hack for editor persistence (can we track).
|
||||
is_file_persistent = is_file_persistent || p_file.begins_with("/home/web_user/");
|
||||
#endif
|
||||
if (is_file_persistent) {
|
||||
os->idb_needs_sync = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OS_Web::update_pwa_state_callback() {
|
||||
if (OS_Web::get_singleton()) {
|
||||
OS_Web::get_singleton()->pwa_is_waiting = true;
|
||||
|
|
@ -275,6 +277,7 @@ OS_Web::OS_Web() {
|
|||
|
||||
if (AudioDriverWeb::is_available()) {
|
||||
audio_drivers.push_back(memnew(AudioDriverWorklet));
|
||||
audio_drivers.push_back(memnew(AudioDriverScriptProcessor));
|
||||
}
|
||||
for (AudioDriverWeb *audio_driver : audio_drivers) {
|
||||
AudioDriverManager::add_driver(audio_driver);
|
||||
|
|
@ -287,4 +290,5 @@ OS_Web::OS_Web() {
|
|||
_set_logger(memnew(CompositeLogger(loggers)));
|
||||
|
||||
FileAccessUnix::close_notification_func = file_access_close_callback;
|
||||
DirAccessUnix::remove_notification_func = dir_access_remove_callback;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#define OS_WEB_H
|
||||
|
||||
#include "audio_driver_web.h"
|
||||
#include "webmidi_driver.h"
|
||||
|
||||
#include "godot_js.h"
|
||||
|
||||
|
|
@ -45,6 +46,8 @@ class OS_Web : public OS_Unix {
|
|||
MainLoop *main_loop = nullptr;
|
||||
List<AudioDriverWeb *> audio_drivers;
|
||||
|
||||
MIDIDriverWebMidi midi_driver;
|
||||
|
||||
bool idb_is_syncing = false;
|
||||
bool idb_available = false;
|
||||
bool idb_needs_sync = false;
|
||||
|
|
@ -53,6 +56,7 @@ class OS_Web : public OS_Unix {
|
|||
WASM_EXPORT static void main_loop_callback();
|
||||
|
||||
WASM_EXPORT static void file_access_close_callback(const String &p_file, int p_flags);
|
||||
WASM_EXPORT static void dir_access_remove_callback(const String &p_file);
|
||||
WASM_EXPORT static void fs_sync_callback();
|
||||
WASM_EXPORT static void update_pwa_state_callback();
|
||||
|
||||
|
|
@ -80,7 +84,7 @@ public:
|
|||
bool main_loop_iterate();
|
||||
|
||||
Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
|
||||
Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
|
||||
Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
|
||||
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;
|
||||
|
|
@ -103,7 +107,7 @@ public:
|
|||
String get_cache_path() const override;
|
||||
String get_config_path() const override;
|
||||
String get_data_path() const override;
|
||||
String get_user_data_dir() const override;
|
||||
String get_user_data_dir(const String &p_user_dir) const override;
|
||||
|
||||
bool is_userfs_persistent() const override;
|
||||
|
||||
|
|
|
|||
639
engine/platform/web/package-lock.json
generated
639
engine/platform/web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,14 +11,14 @@
|
|||
"format": "npm run lint -- --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@html-eslint/eslint-plugin": "^0.24.1",
|
||||
"@html-eslint/parser": "^0.24.1",
|
||||
"@stylistic/eslint-plugin": "^2.1.0",
|
||||
"eslint": "^9.3.0",
|
||||
"@eslint/js": "^9.12.0",
|
||||
"@html-eslint/eslint-plugin": "^0.27.0",
|
||||
"@html-eslint/parser": "^0.27.0",
|
||||
"@stylistic/eslint-plugin": "^2.9.0",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-html": "^8.1.1",
|
||||
"espree": "^10.0.1",
|
||||
"globals": "^15.3.0",
|
||||
"globals": "^15.9.0",
|
||||
"jsdoc": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import os
|
|||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
|
|
@ -38,12 +38,24 @@ def shell_open(url):
|
|||
def serve(root, port, run_browser):
|
||||
os.chdir(root)
|
||||
|
||||
address = ("", port)
|
||||
httpd = DualStackServer(address, CORSRequestHandler)
|
||||
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
if run_browser:
|
||||
# Open the served page in the user's default browser.
|
||||
print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).")
|
||||
shell_open(f"http://127.0.0.1:{port}")
|
||||
print(f"Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this): {url}")
|
||||
shell_open(url)
|
||||
else:
|
||||
print(f"Serving at: {url}")
|
||||
|
||||
test(CORSRequestHandler, DualStackServer, port=port)
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nKeyboard interrupt received, stopping server.")
|
||||
finally:
|
||||
# Clean-up server
|
||||
httpd.server_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
#include "scene/main/scene_tree.h"
|
||||
#include "scene/main/window.h" // SceneTree only forward declares it.
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/web_tools_editor_plugin.h"
|
||||
#endif
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
|
@ -104,6 +108,10 @@ void main_loop_callback() {
|
|||
extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
|
||||
os = new OS_Web();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
WebToolsEditorPlugin::initialize();
|
||||
#endif
|
||||
|
||||
// We must override main when testing is enabled
|
||||
TEST_MAIN_OVERRIDE
|
||||
|
||||
|
|
|
|||
98
engine/platform/web/webmidi_driver.cpp
Normal file
98
engine/platform/web/webmidi_driver.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**************************************************************************/
|
||||
/* webmidi_driver.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 "webmidi_driver.h"
|
||||
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
#include "core/object/callable_method_pointer.h"
|
||||
#endif
|
||||
|
||||
MIDIDriverWebMidi *MIDIDriverWebMidi::get_singleton() {
|
||||
return static_cast<MIDIDriverWebMidi *>(MIDIDriver::get_singleton());
|
||||
}
|
||||
|
||||
Error MIDIDriverWebMidi::open() {
|
||||
Error error = (Error)godot_js_webmidi_open_midi_inputs(&MIDIDriverWebMidi::set_input_names_callback, &MIDIDriverWebMidi::on_midi_message, _event_buffer, MIDIDriverWebMidi::MAX_EVENT_BUFFER_LENGTH);
|
||||
if (error == ERR_UNAVAILABLE) {
|
||||
ERR_PRINT("Web MIDI is not supported on this browser.");
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::close() {
|
||||
get_singleton()->connected_input_names.clear();
|
||||
godot_js_webmidi_close_midi_inputs();
|
||||
}
|
||||
|
||||
MIDIDriverWebMidi::~MIDIDriverWebMidi() {
|
||||
close();
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::set_input_names_callback(int p_size, const char **p_input_names) {
|
||||
Vector<String> input_names;
|
||||
for (int i = 0; i < p_size; i++) {
|
||||
input_names.append(String::utf8(p_input_names[i]));
|
||||
}
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
if (!Thread::is_main_thread()) {
|
||||
callable_mp_static(MIDIDriverWebMidi::_set_input_names_callback).call_deferred(input_names);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
_set_input_names_callback(input_names);
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::_set_input_names_callback(const Vector<String> &p_input_names) {
|
||||
get_singleton()->connected_input_names.clear();
|
||||
for (const String &input_name : p_input_names) {
|
||||
get_singleton()->connected_input_names.push_back(input_name);
|
||||
}
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::on_midi_message(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len) {
|
||||
PackedByteArray data;
|
||||
data.resize(p_data_len);
|
||||
uint8_t *data_ptr = data.ptrw();
|
||||
for (int i = 0; i < p_data_len; i++) {
|
||||
data_ptr[i] = p_data[i];
|
||||
}
|
||||
#ifdef PROXY_TO_PTHREAD_ENABLED
|
||||
if (!Thread::is_main_thread()) {
|
||||
callable_mp_static(MIDIDriverWebMidi::_on_midi_message).call_deferred(p_device_index, p_status, data, p_data_len);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_on_midi_message(p_device_index, p_status, data, p_data_len);
|
||||
}
|
||||
|
||||
void MIDIDriverWebMidi::_on_midi_message(int p_device_index, int p_status, const PackedByteArray &p_data, int p_data_len) {
|
||||
MIDIDriver::send_event(p_device_index, p_status, p_data.ptr(), p_data_len);
|
||||
}
|
||||
61
engine/platform/web/webmidi_driver.h
Normal file
61
engine/platform/web/webmidi_driver.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* webmidi_driver.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef WEBMIDI_DRIVER_H
|
||||
#define WEBMIDI_DRIVER_H
|
||||
|
||||
#include "core/os/midi_driver.h"
|
||||
|
||||
#include "godot_js.h"
|
||||
#include "godot_midi.h"
|
||||
|
||||
class MIDIDriverWebMidi : public MIDIDriver {
|
||||
private:
|
||||
static const int MAX_EVENT_BUFFER_LENGTH = 2;
|
||||
uint8_t _event_buffer[MAX_EVENT_BUFFER_LENGTH];
|
||||
|
||||
public:
|
||||
// Override return type to make writing static callbacks less tedious.
|
||||
static MIDIDriverWebMidi *get_singleton();
|
||||
|
||||
virtual Error open() override;
|
||||
virtual void close() override final;
|
||||
|
||||
MIDIDriverWebMidi() = default;
|
||||
virtual ~MIDIDriverWebMidi();
|
||||
|
||||
WASM_EXPORT static void set_input_names_callback(int p_size, const char **p_input_names);
|
||||
static void _set_input_names_callback(const Vector<String> &p_input_names);
|
||||
|
||||
WASM_EXPORT static void on_midi_message(int p_device_index, int p_status, const uint8_t *p_data, int p_data_len);
|
||||
static void _on_midi_message(int p_device_index, int p_status, const PackedByteArray &p_data, int p_data_len);
|
||||
};
|
||||
|
||||
#endif // WEBMIDI_DRIVER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue