Merge pull request #44076 from Faless/js/4.x_gdnative

[HTML5] Optional GDNative Support
This commit is contained in:
Rémi Verschelde 2020-12-07 15:34:33 +01:00 committed by GitHub
commit e20011b0da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 310 additions and 62 deletions

View File

@ -145,6 +145,7 @@ $GODOT_HEAD_INCLUDE
const EXECUTABLE_NAME = '$GODOT_BASENAME'; const EXECUTABLE_NAME = '$GODOT_BASENAME';
const MAIN_PACK = '$GODOT_BASENAME.pck'; const MAIN_PACK = '$GODOT_BASENAME.pck';
const EXTRA_ARGS = JSON.parse('$GODOT_ARGS'); const EXTRA_ARGS = JSON.parse('$GODOT_ARGS');
const GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS];
const INDETERMINATE_STATUS_STEP_MS = 100; const INDETERMINATE_STATUS_STEP_MS = 100;
const FULL_WINDOW = $GODOT_FULL_WINDOW; const FULL_WINDOW = $GODOT_FULL_WINDOW;
@ -263,6 +264,7 @@ $GODOT_HEAD_INCLUDE
} else { } else {
setStatusMode('indeterminate'); setStatusMode('indeterminate');
engine.setCanvas(canvas); engine.setCanvas(canvas);
engine.setGDNativeLibraries(GDNATIVE_LIBS);
engine.startGame(EXECUTABLE_NAME, MAIN_PACK, EXTRA_ARGS).then(() => { engine.startGame(EXECUTABLE_NAME, MAIN_PACK, EXTRA_ARGS).then(() => {
setStatusMode('hidden'); setStatusMode('hidden');
initializing = false; initializing = false;

View File

@ -308,11 +308,11 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
platform_android.library_extension = "*.so"; platform_android.library_extension = "*.so";
platforms["Android"] = platform_android; platforms["Android"] = platform_android;
// TODO: Javascript platform is not supported yet NativePlatformConfig platform_html5;
// NativePlatformConfig platform_html5; platform_html5.name = "HTML5";
// platform_html5.name = "HTML5"; platform_html5.entries.push_back("wasm32");
// platform_html5.library_extension = "*.wasm"; platform_html5.library_extension = "*.wasm";
// platforms["Javascript"] = platform_html5; platforms["HTML5"] = platform_html5;
NativePlatformConfig platform_ios; NativePlatformConfig platform_ios;
platform_ios.name = "iOS"; platform_ios.name = "iOS";

View File

@ -90,6 +90,7 @@ const GodotRTCDataChannel = {
}, },
}, },
godot_js_rtc_datachannel_ready_state_get__sig: 'ii',
godot_js_rtc_datachannel_ready_state_get: function (p_id) { godot_js_rtc_datachannel_ready_state_get: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -109,6 +110,7 @@ const GodotRTCDataChannel = {
} }
}, },
godot_js_rtc_datachannel_send__sig: 'iiiii',
godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) { godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -129,14 +131,17 @@ const GodotRTCDataChannel = {
return 0; return 0;
}, },
godot_js_rtc_datachannel_is_ordered__sig: 'ii',
godot_js_rtc_datachannel_is_ordered: function (p_id) { godot_js_rtc_datachannel_is_ordered: function (p_id) {
return IDHandler.get_prop(p_id, 'ordered', true); return IDHandler.get_prop(p_id, 'ordered', true);
}, },
godot_js_rtc_datachannel_id_get__sig: 'ii',
godot_js_rtc_datachannel_id_get: function (p_id) { godot_js_rtc_datachannel_id_get: function (p_id) {
return IDHandler.get_prop(p_id, 'id', 65535); return IDHandler.get_prop(p_id, 'id', 65535);
}, },
godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii',
godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) { godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -151,14 +156,17 @@ const GodotRTCDataChannel = {
return 65535; return 65535;
}, },
godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii',
godot_js_rtc_datachannel_max_retransmits_get: function (p_id) { godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
return IDHandler.get_prop(p_id, 'maxRetransmits', 65535); return IDHandler.get_prop(p_id, 'maxRetransmits', 65535);
}, },
godot_js_rtc_datachannel_is_negotiated: function (p_id, p_def) { godot_js_rtc_datachannel_is_negotiated__sig: 'ii',
godot_js_rtc_datachannel_is_negotiated: function (p_id) {
return IDHandler.get_prop(p_id, 'negotiated', 65535); return IDHandler.get_prop(p_id, 'negotiated', 65535);
}, },
godot_js_rtc_datachannel_label_get__sig: 'ii',
godot_js_rtc_datachannel_label_get: function (p_id) { godot_js_rtc_datachannel_label_get: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref || !ref.label) { if (!ref || !ref.label) {
@ -167,6 +175,7 @@ const GodotRTCDataChannel = {
return GodotRuntime.allocString(ref.label); return GodotRuntime.allocString(ref.label);
}, },
godot_js_rtc_datachannel_protocol_get__sig: 'ii',
godot_js_rtc_datachannel_protocol_get: function (p_id) { godot_js_rtc_datachannel_protocol_get: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref || !ref.protocol) { if (!ref || !ref.protocol) {
@ -175,11 +184,13 @@ const GodotRTCDataChannel = {
return GodotRuntime.allocString(ref.protocol); return GodotRuntime.allocString(ref.protocol);
}, },
godot_js_rtc_datachannel_destroy__sig: 'vi',
godot_js_rtc_datachannel_destroy: function (p_id) { godot_js_rtc_datachannel_destroy: function (p_id) {
GodotRTCDataChannel.close(p_id); GodotRTCDataChannel.close(p_id);
IDHandler.remove(p_id); IDHandler.remove(p_id);
}, },
godot_js_rtc_datachannel_connect__sig: 'viiiiii',
godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) { godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref); const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref); const onmessage = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
@ -188,6 +199,7 @@ const GodotRTCDataChannel = {
GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose); GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
}, },
godot_js_rtc_datachannel_close__sig: 'vi',
godot_js_rtc_datachannel_close: function (p_id) { godot_js_rtc_datachannel_close: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -280,6 +292,7 @@ const GodotRTCPeerConnection = {
}, },
}, },
godot_js_rtc_pc_create__sig: 'iiiiii',
godot_js_rtc_pc_create: function (p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) { godot_js_rtc_pc_create: function (p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) {
const onstatechange = GodotRuntime.get_func(p_on_state_change).bind(null, p_ref); const onstatechange = GodotRuntime.get_func(p_on_state_change).bind(null, p_ref);
const oncandidate = GodotRuntime.get_func(p_on_candidate).bind(null, p_ref); const oncandidate = GodotRuntime.get_func(p_on_candidate).bind(null, p_ref);
@ -302,6 +315,7 @@ const GodotRTCPeerConnection = {
return id; return id;
}, },
godot_js_rtc_pc_close__sig: 'vi',
godot_js_rtc_pc_close: function (p_id) { godot_js_rtc_pc_close: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -310,6 +324,7 @@ const GodotRTCPeerConnection = {
ref.close(); ref.close();
}, },
godot_js_rtc_pc_destroy__sig: 'vi',
godot_js_rtc_pc_destroy: function (p_id) { godot_js_rtc_pc_destroy: function (p_id) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -321,6 +336,7 @@ const GodotRTCPeerConnection = {
IDHandler.remove(p_id); IDHandler.remove(p_id);
}, },
godot_js_rtc_pc_offer_create__sig: 'viiii',
godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) { godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -335,6 +351,7 @@ const GodotRTCPeerConnection = {
}); });
}, },
godot_js_rtc_pc_local_description_set__sig: 'viiiii',
godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) { godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -351,6 +368,7 @@ const GodotRTCPeerConnection = {
}); });
}, },
godot_js_rtc_pc_remote_description_set__sig: 'viiiiii',
godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) { godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -375,6 +393,7 @@ const GodotRTCPeerConnection = {
}); });
}, },
godot_js_rtc_pc_ice_candidate_add__sig: 'viiii',
godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) { godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);
if (!ref) { if (!ref) {
@ -390,6 +409,7 @@ const GodotRTCPeerConnection = {
}, },
godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'], godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
godot_js_rtc_pc_datachannel_create__sig: 'iiii',
godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) { godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) {
try { try {
const ref = IDHandler.get(p_id); const ref = IDHandler.get(p_id);

View File

@ -135,6 +135,7 @@ const GodotWebSocket = {
}, },
}, },
godot_js_websocket_create__sig: 'iiiiiiii',
godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) { godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref); const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
const on_message = GodotRuntime.get_func(p_on_message).bind(null, p_ref); const on_message = GodotRuntime.get_func(p_on_message).bind(null, p_ref);
@ -156,6 +157,7 @@ const GodotWebSocket = {
return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close); return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close);
}, },
godot_js_websocket_send__sig: 'iiiii',
godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) { godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) {
const bytes_array = new Uint8Array(p_buf_len); const bytes_array = new Uint8Array(p_buf_len);
let i = 0; let i = 0;
@ -169,12 +171,14 @@ const GodotWebSocket = {
return GodotWebSocket.send(p_id, out); return GodotWebSocket.send(p_id, out);
}, },
godot_js_websocket_close__sig: 'viii',
godot_js_websocket_close: function (p_id, p_code, p_reason) { godot_js_websocket_close: function (p_id, p_code, p_reason) {
const code = p_code; const code = p_code;
const reason = GodotRuntime.parseString(p_reason); const reason = GodotRuntime.parseString(p_reason);
GodotWebSocket.close(p_id, code, reason); GodotWebSocket.close(p_id, code, reason);
}, },
godot_js_websocket_destroy__sig: 'vi',
godot_js_websocket_destroy: function (p_id) { godot_js_websocket_destroy: function (p_id) {
GodotWebSocket.destroy(p_id); GodotWebSocket.destroy(p_id);
}, },

View File

@ -12,13 +12,8 @@ javascript_files = [
"api/javascript_tools_editor_plugin.cpp", "api/javascript_tools_editor_plugin.cpp",
] ]
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] sys_env = env.Clone()
if env["threads_enabled"]: sys_env.AddJSLibraries(
build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
build = env.add_program(build_targets, javascript_files)
env.AddJSLibraries(
[ [
"js/libs/library_godot_audio.js", "js/libs/library_godot_audio.js",
"js/libs/library_godot_display.js", "js/libs/library_godot_display.js",
@ -29,12 +24,48 @@ env.AddJSLibraries(
) )
if env["tools"]: if env["tools"]:
env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"])
if env["javascript_eval"]: if env["javascript_eval"]:
env.AddJSLibraries(["js/libs/library_godot_eval.js"]) sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"])
for lib in env["JS_LIBS"]: for lib in sys_env["JS_LIBS"]:
env.Append(LINKFLAGS=["--js-library", lib]) sys_env.Append(LINKFLAGS=["--js-library", lib])
env.Depends(build, env["JS_LIBS"])
build = []
if env["gdnative_enabled"]:
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
# Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
sys_env["LIBS"] = []
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
# JS prepended to the module code loading the side library.
sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")])
# Configure it as a main module (dynamic linking support).
sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"])
sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"])
sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"])
sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"])
# Force exporting the standard library (printf, malloc, etc.)
sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi"
# The main emscripten runtime, with exported standard libraries.
sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"])
sys_env.Depends(sys, "js/dynlink.pre.js")
# The side library, containing all Godot code.
wasm_env = env.Clone()
wasm_env.Append(CPPDEFINES=["WASM_GDNATIVE"]) # So that OS knows it can run GDNative libraries.
wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"])
wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"])
wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", javascript_files)
build = [sys[0], sys[1], wasm[0]]
else:
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
if env["threads_enabled"]:
build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
# We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
sys_env.Append(LIBS=["idbfs.js"])
build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"])
sys_env.Depends(build[0], sys_env["JS_LIBS"])
engine = [ engine = [
"js/engine/preloader.js", "js/engine/preloader.js",
@ -61,8 +92,11 @@ out_files = [
] ]
html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html"
in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"] in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"]
if env["threads_enabled"]: if env["gdnative_enabled"]:
in_files.append(build[2]) in_files.append(build[2]) # Runtime
out_files.append(zip_dir.File(binary_name + ".side.wasm"))
elif env["threads_enabled"]:
in_files.append(build[2]) # Worker
out_files.append(zip_dir.File(binary_name + ".worker.js")) out_files.append(zip_dir.File(binary_name + ".worker.js"))
zip_files = env.InstallAs(out_files, in_files) zip_files = env.InstallAs(out_files, in_files)

View File

@ -23,6 +23,7 @@ def get_opts():
# eval() can be a security concern, so it can be disabled. # eval() can be a security concern, so it can be disabled.
BoolVariable("javascript_eval", "Enable JavaScript eval interface", True), BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False), BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False),
BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False),
BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
] ]
@ -85,10 +86,8 @@ def configure(env):
# LTO # LTO
if env["use_lto"]: if env["use_lto"]:
env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"]) env.Append(CCFLAGS=["-flto=full"])
env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"]) env.Append(LINKFLAGS=["-flto=full"])
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
# Closure compiler # Closure compiler
if env["use_closure_compiler"]: if env["use_closure_compiler"]:
@ -135,6 +134,9 @@ def configure(env):
if env["javascript_eval"]: if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
if env["threads_enabled"] and env["gdnative_enabled"]:
raise Exception("Threads and GDNative support can't be both enabled due to WebAssembly limitations")
# Thread support (via SharedArrayBuffer). # Thread support (via SharedArrayBuffer).
if env["threads_enabled"]: if env["threads_enabled"]:
env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
@ -146,14 +148,15 @@ def configure(env):
else: else:
env.Append(CPPDEFINES=["NO_THREADS"]) env.Append(CPPDEFINES=["NO_THREADS"])
if env["gdnative_enabled"]:
env.Append(CCFLAGS=["-s", "RELOCATABLE=1"])
env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"])
env.extra_suffix = ".gdnative" + env.extra_suffix
# Reduce code size by generating less support code (e.g. skip NodeJS support). # Reduce code size by generating less support code (e.g. skip NodeJS support).
env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
# We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to # Wrap the JavaScript support code around a closure named Godot.
# be linked explicitly.
env.Append(LIBS=["idbfs.js"])
env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"]) env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
# Allow increasing memory buffer size during runtime. This is efficient # Allow increasing memory buffer size during runtime. This is efficient
@ -164,12 +167,14 @@ def configure(env):
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"]) env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
# Do not call main immediately when the support code is ready.
env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"]) env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"])
# Allow use to take control of swapping WebGL buffers. # Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
# callMain for manual start, FS for preloading, PATH and ERRNO_CODES for BrowserFS. # callMain for manual start.
env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"]) env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"])
# Add code that allow exiting runtime. # Add code that allow exiting runtime.
env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"]) env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])

View File

@ -85,36 +85,44 @@ public:
// Wrong protocol // Wrong protocol
ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); const String cache_path = EditorSettings::get_singleton()->get_cache_dir();
const String basereq = "/tmp_js_export"; const String basereq = "/tmp_js_export";
String ctype = ""; String filepath;
String ctype;
if (req[1] == basereq + ".html") { if (req[1] == basereq + ".html") {
filepath += ".html"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "text/html"; ctype = "text/html";
} else if (req[1] == basereq + ".js") { } else if (req[1] == basereq + ".js") {
filepath += ".js"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/javascript"; ctype = "application/javascript";
} else if (req[1] == basereq + ".audio.worklet.js") { } else if (req[1] == basereq + ".audio.worklet.js") {
filepath += ".audio.worklet.js"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/javascript"; ctype = "application/javascript";
} else if (req[1] == basereq + ".worker.js") { } else if (req[1] == basereq + ".worker.js") {
filepath += ".worker.js"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/javascript"; ctype = "application/javascript";
} else if (req[1] == basereq + ".pck") { } else if (req[1] == basereq + ".pck") {
filepath += ".pck"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/octet-stream"; ctype = "application/octet-stream";
} else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") { } else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") {
// Also allow serving the generated favicon for a smoother loading experience. // Also allow serving the generated favicon for a smoother loading experience.
if (req[1] == "/favicon.png") { if (req[1] == "/favicon.png") {
filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"); filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png");
} else { } else {
filepath += ".png"; filepath = basereq + ".png";
} }
ctype = "image/png"; ctype = "image/png";
} else if (req[1] == basereq + ".wasm") { } else if (req[1] == basereq + ".side.wasm") {
filepath += ".wasm"; filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/wasm"; ctype = "application/wasm";
} else { } else if (req[1] == basereq + ".wasm") {
filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/wasm";
} else if (req[1].ends_with(".wasm")) {
filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous?
ctype = "application/wasm";
}
if (filepath.empty() || !FileAccess::exists(filepath)) {
String s = "HTTP/1.1 404 Not Found\r\n"; String s = "HTTP/1.1 404 Not Found\r\n";
s += "Connection: Close\r\n"; s += "Connection: Close\r\n";
s += "\r\n"; s += "\r\n";
@ -205,7 +213,33 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
Ref<ImageTexture> stop_icon; Ref<ImageTexture> stop_icon;
int menu_options; int menu_options;
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags); enum ExportMode {
EXPORT_MODE_NORMAL = 0,
EXPORT_MODE_THREADS = 1,
EXPORT_MODE_GDNATIVE = 2,
};
String _get_template_name(ExportMode p_mode, bool p_debug) const {
String name = "webassembly";
switch (p_mode) {
case EXPORT_MODE_THREADS:
name += "_threads";
break;
case EXPORT_MODE_GDNATIVE:
name += "_gdnative";
break;
default:
break;
}
if (p_debug) {
name += "_debug.zip";
} else {
name += "_release.zip";
}
return name;
}
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);
private: private:
Ref<EditorHTTPServer> server; Ref<EditorHTTPServer> server;
@ -250,7 +284,7 @@ public:
~EditorExportPlatformJavaScript(); ~EditorExportPlatformJavaScript();
}; };
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags) { void EditorExportPlatformJavaScript::_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) {
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
String str_export; String str_export;
Vector<String> lines = str_template.split("\n"); Vector<String> lines = str_template.split("\n");
@ -258,6 +292,10 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
String flags_json; String flags_json;
gen_export_flags(flags, p_flags); gen_export_flags(flags, p_flags);
flags_json = JSON::print(flags); flags_json = JSON::print(flags);
String libs;
for (int i = 0; i < p_shared_objects.size(); i++) {
libs += "\"" + p_shared_objects[i].path.get_file() + "\",";
}
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
String current_line = lines[i]; String current_line = lines[i];
@ -265,6 +303,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name")); current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name"));
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false"); current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false");
current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs);
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
current_line = current_line.replace("$GODOT_ARGS", flags_json); current_line = current_line.replace("$GODOT_ARGS", flags_json);
str_export += current_line + "\n"; str_export += current_line + "\n";
@ -291,12 +330,19 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP
r_features->push_back("etc2"); r_features->push_back("etc2");
} }
} }
ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
if (mode == EXPORT_MODE_THREADS) {
r_features->push_back("threads");
} else if (mode == EXPORT_MODE_GDNATIVE) {
r_features->push_back("wasm32");
}
} }
void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) { void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "variant/export_type", PROPERTY_HINT_ENUM, "Regular,Threads,GDNative"), 0)); // Export type.
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@ -320,11 +366,11 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err; String err;
bool valid = false; bool valid = false;
ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
// Look for export templates (first official, and if defined custom templates). // Look for export templates (first official, and if defined custom templates).
bool dvalid = exists_export_template(_get_template_name(mode, true), &err);
bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err); bool rvalid = exists_export_template(_get_template_name(mode, false), &err);
bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err);
if (p_preset->get("custom_template/debug") != "") { if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@ -377,11 +423,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
template_path = template_path.strip_edges(); template_path = template_path.strip_edges();
if (template_path == String()) { if (template_path == String()) {
if (p_debug) { ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); template_path = find_export_template(_get_template_name(mode, p_debug));
} else {
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE);
}
} }
if (!DirAccess::exists(p_path.get_base_dir())) { if (!DirAccess::exists(p_path.get_base_dir())) {
@ -393,12 +436,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
return ERR_FILE_NOT_FOUND; return ERR_FILE_NOT_FOUND;
} }
Vector<SharedObject> shared_objects;
String pck_path = p_path.get_basename() + ".pck"; String pck_path = p_path.get_basename() + ".pck";
Error error = save_pack(p_preset, pck_path); Error error = save_pack(p_preset, pck_path, &shared_objects);
if (error != OK) { if (error != OK) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path); EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
return error; return error;
} }
DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < shared_objects.size(); i++) {
String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file());
error = da->copy(shared_objects[i].path, dst);
if (error != OK) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file());
memdelete(da);
return error;
}
}
memdelete(da);
FileAccess *src_f = nullptr; FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f); zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
@ -437,14 +492,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
if (!custom_html.empty()) { if (!custom_html.empty()) {
continue; continue;
} }
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
file = p_path.get_file(); file = p_path.get_file();
} else if (file == "godot.js") { } else if (file == "godot.js") {
file = p_path.get_file().get_basename() + ".js"; file = p_path.get_file().get_basename() + ".js";
} else if (file == "godot.worker.js") { } else if (file == "godot.worker.js") {
file = p_path.get_file().get_basename() + ".worker.js"; file = p_path.get_file().get_basename() + ".worker.js";
} else if (file == "godot.side.wasm") {
file = p_path.get_file().get_basename() + ".side.wasm";
} else if (file == "godot.audio.worklet.js") { } else if (file == "godot.audio.worklet.js") {
file = p_path.get_file().get_basename() + ".audio.worklet.js"; file = p_path.get_file().get_basename() + ".audio.worklet.js";
@ -475,7 +534,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
buf.resize(f->get_len()); buf.resize(f->get_len());
f->get_buffer(buf.ptrw(), buf.size()); f->get_buffer(buf.ptrw(), buf.size());
memdelete(f); memdelete(f);
_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags); _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
f = FileAccess::open(p_path, FileAccess::WRITE); f = FileAccess::open(p_path, FileAccess::WRITE);
if (!f) { if (!f) {
@ -577,6 +636,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
DirAccess::remove_file_or_error(basepath + ".audio.worklet.js"); DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png"); DirAccess::remove_file_or_error(basepath + ".png");
DirAccess::remove_file_or_error(basepath + ".side.wasm");
DirAccess::remove_file_or_error(basepath + ".wasm"); DirAccess::remove_file_or_error(basepath + ".wasm");
DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png")); DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"));
return err; return err;

View File

@ -48,7 +48,7 @@ extern void godot_audio_capture_stop();
typedef int32_t GodotAudioState[4]; typedef int32_t GodotAudioState[4];
extern void godot_audio_worklet_create(int p_channels); extern void godot_audio_worklet_create(int p_channels);
extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state); extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state);
extern void godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value); extern int godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value);
extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx); extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx);
extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout); extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout);

View File

@ -47,7 +47,7 @@ typedef enum {
extern int godot_xhr_new(); extern int godot_xhr_new();
extern void godot_xhr_reset(int p_xhr_id); extern void godot_xhr_reset(int p_xhr_id);
extern bool godot_xhr_free(int p_xhr_id); extern void godot_xhr_free(int p_xhr_id);
extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr); extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr);

View File

@ -75,7 +75,7 @@ void main_loop_callback() {
} }
/// When calling main, it is assumed FS is setup and synced. /// When calling main, it is assumed FS is setup and synced.
int main(int argc, char *argv[]) { extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
os = new OS_JavaScript(); os = new OS_JavaScript();
// We must override main when testing is enabled // We must override main when testing is enabled

View File

@ -0,0 +1,35 @@
/*************************************************************************/
/* javascript_runtime.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
extern int godot_js_main(int argc, char *argv[]);
int main(int argc, char *argv[]) {
return godot_js_main(argc, argv);
}

View File

@ -0,0 +1 @@
Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []);

View File

@ -34,6 +34,7 @@ const Engine = (function () {
this.onExecute = null; this.onExecute = null;
this.onExit = null; this.onExit = null;
this.persistentPaths = ['/userfs']; this.persistentPaths = ['/userfs'];
this.gdnativeLibs = [];
} }
Engine.prototype.init = /** @param {string=} basePath */ function (basePath) { Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
@ -58,6 +59,10 @@ const Engine = (function () {
initPromise = new Promise(function (resolve, reject) { initPromise = new Promise(function (resolve, reject) {
config['locateFile'] = Utils.createLocateRewrite(loadPath); config['locateFile'] = Utils.createLocateRewrite(loadPath);
config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
// Emscripten configuration.
config['thisProgram'] = me.executableName;
config['noExitRuntime'] = true;
config['dynamicLibraries'] = me.gdnativeLibs;
Godot(config).then(function (module) { Godot(config).then(function (module) {
module['initFS'](me.persistentPaths).then(function (fs_err) { module['initFS'](me.persistentPaths).then(function (fs_err) {
me.rtenv = module; me.rtenv = module;
@ -119,9 +124,6 @@ const Engine = (function () {
locale = navigator.languages ? navigator.languages[0] : navigator.language; locale = navigator.languages ? navigator.languages[0] : navigator.language;
locale = locale.split('.')[0]; locale = locale.split('.')[0];
} }
// Emscripten configuration.
me.rtenv['thisProgram'] = me.executableName;
me.rtenv['noExitRuntime'] = true;
// Godot configuration. // Godot configuration.
me.rtenv['initConfig']({ me.rtenv['initConfig']({
'resizeCanvasOnStart': me.resizeCanvasOnStart, 'resizeCanvasOnStart': me.resizeCanvasOnStart,
@ -249,6 +251,10 @@ const Engine = (function () {
this.persistentPaths = persistentPaths; this.persistentPaths = persistentPaths;
}; };
Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) {
this.gdnativeLibs = gdnativeLibs;
};
Engine.prototype.requestQuit = function () { Engine.prototype.requestQuit = function () {
if (this.rtenv) { if (this.rtenv) {
this.rtenv['request_quit'](); this.rtenv['request_quit']();
@ -277,6 +283,7 @@ const Engine = (function () {
Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries;
Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
return Engine; return Engine;
}()); }());

View File

@ -8,6 +8,8 @@ const Utils = { // eslint-disable-line no-unused-vars
return `${execName}.audio.worklet.js`; return `${execName}.audio.worklet.js`;
} else if (path.endsWith('.js')) { } else if (path.endsWith('.js')) {
return `${execName}.js`; return `${execName}.js`;
} else if (path.endsWith('.side.wasm')) {
return `${execName}.side.wasm`;
} else if (path.endsWith('.wasm')) { } else if (path.endsWith('.wasm')) {
return `${execName}.wasm`; return `${execName}.wasm`;
} }

View File

@ -137,6 +137,7 @@ const GodotAudio = {
}, },
}, },
godot_audio_is_available__sig: 'i',
godot_audio_is_available__proxy: 'sync', godot_audio_is_available__proxy: 'sync',
godot_audio_is_available: function () { godot_audio_is_available: function () {
if (!(window.AudioContext || window.webkitAudioContext)) { if (!(window.AudioContext || window.webkitAudioContext)) {
@ -145,12 +146,14 @@ const GodotAudio = {
return 1; return 1;
}, },
godot_audio_init__sig: 'iiiii',
godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) { godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
const statechange = GodotRuntime.get_func(p_state_change); const statechange = GodotRuntime.get_func(p_state_change);
const latencyupdate = GodotRuntime.get_func(p_latency_update); const latencyupdate = GodotRuntime.get_func(p_latency_update);
return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
}, },
godot_audio_resume__sig: 'v',
godot_audio_resume: function () { godot_audio_resume: function () {
if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
GodotAudio.ctx.resume(); GodotAudio.ctx.resume();
@ -158,6 +161,7 @@ const GodotAudio = {
}, },
godot_audio_capture_start__proxy: 'sync', godot_audio_capture_start__proxy: 'sync',
godot_audio_capture_start__sig: 'v',
godot_audio_capture_start: function () { godot_audio_capture_start: function () {
if (GodotAudio.input) { if (GodotAudio.input) {
return; // Already started. return; // Already started.
@ -168,6 +172,7 @@ const GodotAudio = {
}, },
godot_audio_capture_stop__proxy: 'sync', godot_audio_capture_stop__proxy: 'sync',
godot_audio_capture_stop__sig: 'v',
godot_audio_capture_stop: function () { godot_audio_capture_stop: function () {
if (GodotAudio.input) { if (GodotAudio.input) {
const tracks = GodotAudio.input['mediaStream']['getTracks'](); const tracks = GodotAudio.input['mediaStream']['getTracks']();
@ -241,10 +246,12 @@ const GodotAudioWorklet = {
}, },
}, },
godot_audio_worklet_create__sig: 'vi',
godot_audio_worklet_create: function (channels) { godot_audio_worklet_create: function (channels) {
GodotAudioWorklet.create(channels); GodotAudioWorklet.create(channels);
}, },
godot_audio_worklet_start__sig: 'viiiii',
godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
@ -252,15 +259,18 @@ const GodotAudioWorklet = {
GodotAudioWorklet.start(in_buffer, out_buffer, state); GodotAudioWorklet.start(in_buffer, out_buffer, state);
}, },
godot_audio_worklet_state_wait__sig: 'iiii',
godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) { godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) {
Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
return Atomics.load(HEAP32, (p_state >> 2) + p_idx); return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
}, },
godot_audio_worklet_state_add__sig: 'iiii',
godot_audio_worklet_state_add: function (p_state, p_idx, p_value) { godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
}, },
godot_audio_worklet_state_get__sig: 'iii',
godot_audio_worklet_state_get: function (p_state, p_idx) { godot_audio_worklet_state_get: function (p_state, p_idx) {
return Atomics.load(HEAP32, (p_state >> 2) + p_idx); return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
}, },
@ -330,10 +340,12 @@ const GodotAudioScript = {
}, },
}, },
godot_audio_script_create__sig: 'iii',
godot_audio_script_create: function (buffer_length, channel_count) { godot_audio_script_create: function (buffer_length, channel_count) {
return GodotAudioScript.create(buffer_length, channel_count); return GodotAudioScript.create(buffer_length, channel_count);
}, },
godot_audio_script_start__sig: 'viiiii',
godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
const onprocess = GodotRuntime.get_func(p_cb); const onprocess = GodotRuntime.get_func(p_cb);
GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess); GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);

View File

@ -280,6 +280,7 @@ const GodotDisplay = {
window_icon: '', window_icon: '',
}, },
godot_js_display_is_swap_ok_cancel__sig: 'i',
godot_js_display_is_swap_ok_cancel: function () { godot_js_display_is_swap_ok_cancel: function () {
const win = (['Windows', 'Win64', 'Win32', 'WinCE']); const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
const plat = navigator.platform || ''; const plat = navigator.platform || '';
@ -289,10 +290,12 @@ const GodotDisplay = {
return 0; return 0;
}, },
godot_js_display_alert__sig: 'vi',
godot_js_display_alert: function (p_text) { godot_js_display_alert: function (p_text) {
window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert
}, },
godot_js_display_pixel_ratio_get__sig: 'f',
godot_js_display_pixel_ratio_get: function () { godot_js_display_pixel_ratio_get: function () {
return window.devicePixelRatio || 1; return window.devicePixelRatio || 1;
}, },
@ -300,14 +303,17 @@ const GodotDisplay = {
/* /*
* Canvas * Canvas
*/ */
godot_js_display_canvas_focus__sig: 'v',
godot_js_display_canvas_focus: function () { godot_js_display_canvas_focus: function () {
GodotConfig.canvas.focus(); GodotConfig.canvas.focus();
}, },
godot_js_display_canvas_is_focused__sig: 'i',
godot_js_display_canvas_is_focused: function () { godot_js_display_canvas_is_focused: function () {
return document.activeElement === GodotConfig.canvas; return document.activeElement === GodotConfig.canvas;
}, },
godot_js_display_canvas_bounding_rect_position_get__sig: 'vii',
godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) { godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) {
const brect = GodotConfig.canvas.getBoundingClientRect(); const brect = GodotConfig.canvas.getBoundingClientRect();
GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); GodotRuntime.setHeapValue(r_x, brect.x, 'i32');
@ -317,6 +323,7 @@ const GodotDisplay = {
/* /*
* Touchscreen * Touchscreen
*/ */
godot_js_display_touchscreen_is_available__sig: 'i',
godot_js_display_touchscreen_is_available: function () { godot_js_display_touchscreen_is_available: function () {
return 'ontouchstart' in window; return 'ontouchstart' in window;
}, },
@ -324,6 +331,7 @@ const GodotDisplay = {
/* /*
* Clipboard * Clipboard
*/ */
godot_js_display_clipboard_set__sig: 'ii',
godot_js_display_clipboard_set: function (p_text) { godot_js_display_clipboard_set: function (p_text) {
const text = GodotRuntime.parseString(p_text); const text = GodotRuntime.parseString(p_text);
if (!navigator.clipboard || !navigator.clipboard.writeText) { if (!navigator.clipboard || !navigator.clipboard.writeText) {
@ -336,6 +344,7 @@ const GodotDisplay = {
return 0; return 0;
}, },
godot_js_display_clipboard_get__sig: 'ii',
godot_js_display_clipboard_get: function (callback) { godot_js_display_clipboard_get: function (callback) {
const func = GodotRuntime.get_func(callback); const func = GodotRuntime.get_func(callback);
try { try {
@ -354,6 +363,7 @@ const GodotDisplay = {
/* /*
* Window * Window
*/ */
godot_js_display_window_request_fullscreen__sig: 'v',
godot_js_display_window_request_fullscreen: function () { godot_js_display_window_request_fullscreen: function () {
const canvas = GodotConfig.canvas; const canvas = GodotConfig.canvas;
(canvas.requestFullscreen || canvas.msRequestFullscreen (canvas.requestFullscreen || canvas.msRequestFullscreen
@ -362,10 +372,12 @@ const GodotDisplay = {
).call(canvas); ).call(canvas);
}, },
godot_js_display_window_title_set__sig: 'vi',
godot_js_display_window_title_set: function (p_data) { godot_js_display_window_title_set: function (p_data) {
document.title = GodotRuntime.parseString(p_data); document.title = GodotRuntime.parseString(p_data);
}, },
godot_js_display_window_icon_set__sig: 'vii',
godot_js_display_window_icon_set: function (p_ptr, p_len) { godot_js_display_window_icon_set: function (p_ptr, p_len) {
let link = document.getElementById('-gd-engine-icon'); let link = document.getElementById('-gd-engine-icon');
if (link === null) { if (link === null) {
@ -386,6 +398,7 @@ const GodotDisplay = {
/* /*
* Cursor * Cursor
*/ */
godot_js_display_cursor_set_visible__sig: 'vi',
godot_js_display_cursor_set_visible: function (p_visible) { godot_js_display_cursor_set_visible: function (p_visible) {
const visible = p_visible !== 0; const visible = p_visible !== 0;
if (visible === GodotDisplayCursor.visible) { if (visible === GodotDisplayCursor.visible) {
@ -399,14 +412,17 @@ const GodotDisplay = {
} }
}, },
godot_js_display_cursor_is_hidden__sig: 'i',
godot_js_display_cursor_is_hidden: function () { godot_js_display_cursor_is_hidden: function () {
return !GodotDisplayCursor.visible; return !GodotDisplayCursor.visible;
}, },
godot_js_display_cursor_set_shape__sig: 'vi',
godot_js_display_cursor_set_shape: function (p_string) { godot_js_display_cursor_set_shape: function (p_string) {
GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string)); GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string));
}, },
godot_js_display_cursor_set_custom_shape__sig: 'viiiii',
godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
const shape = GodotRuntime.parseString(p_shape); const shape = GodotRuntime.parseString(p_shape);
const old_shape = GodotDisplayCursor.cursors[shape]; const old_shape = GodotDisplayCursor.cursors[shape];
@ -432,6 +448,7 @@ const GodotDisplay = {
/* /*
* Listeners * Listeners
*/ */
godot_js_display_notification_cb__sig: 'viiiii',
godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) {
const canvas = GodotConfig.canvas; const canvas = GodotConfig.canvas;
const func = GodotRuntime.get_func(callback); const func = GodotRuntime.get_func(callback);
@ -443,6 +460,7 @@ const GodotDisplay = {
}); });
}, },
godot_js_display_paste_cb__sig: 'vi',
godot_js_display_paste_cb: function (callback) { godot_js_display_paste_cb: function (callback) {
const func = GodotRuntime.get_func(callback); const func = GodotRuntime.get_func(callback);
GodotDisplayListeners.add(window, 'paste', function (evt) { GodotDisplayListeners.add(window, 'paste', function (evt) {
@ -453,6 +471,7 @@ const GodotDisplay = {
}, false); }, false);
}, },
godot_js_display_drop_files_cb__sig: 'vi',
godot_js_display_drop_files_cb: function (callback) { godot_js_display_drop_files_cb: function (callback) {
const func = GodotRuntime.get_func(callback); const func = GodotRuntime.get_func(callback);
const dropFiles = function (files) { const dropFiles = function (files) {

View File

@ -30,6 +30,7 @@
const GodotEditorTools = { const GodotEditorTools = {
godot_js_editor_download_file__deps: ['$FS'], godot_js_editor_download_file__deps: ['$FS'],
godot_js_editor_download_file__sig: 'viii',
godot_js_editor_download_file: function (p_path, p_name, p_mime) { godot_js_editor_download_file: function (p_path, p_name, p_mime) {
const path = GodotRuntime.parseString(p_path); const path = GodotRuntime.parseString(p_path);
const name = GodotRuntime.parseString(p_name); const name = GodotRuntime.parseString(p_name);

View File

@ -30,6 +30,7 @@
const GodotEval = { const GodotEval = {
godot_js_eval__deps: ['$GodotRuntime'], godot_js_eval__deps: ['$GodotRuntime'],
godot_js_eval__sig: 'iiiiiii',
godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) {
const js_code = GodotRuntime.parseString(p_js); const js_code = GodotRuntime.parseString(p_js);
let eval_ret = null; let eval_ret = null;

View File

@ -50,6 +50,7 @@ const GodotHTTPRequest = {
}, },
}, },
godot_xhr_new__sig: 'i',
godot_xhr_new: function () { godot_xhr_new: function () {
const newId = GodotHTTPRequest.getUnusedRequestId(); const newId = GodotHTTPRequest.getUnusedRequestId();
GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); GodotHTTPRequest.requests[newId] = new XMLHttpRequest();
@ -57,30 +58,36 @@ const GodotHTTPRequest = {
return newId; return newId;
}, },
godot_xhr_reset__sig: 'vi',
godot_xhr_reset: function (xhrId) { godot_xhr_reset: function (xhrId) {
GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest();
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]);
}, },
godot_xhr_free__sig: 'vi',
godot_xhr_free: function (xhrId) { godot_xhr_free: function (xhrId) {
GodotHTTPRequest.requests[xhrId].abort(); GodotHTTPRequest.requests[xhrId].abort();
GodotHTTPRequest.requests[xhrId] = null; GodotHTTPRequest.requests[xhrId] = null;
}, },
godot_xhr_open__sig: 'viiiii',
godot_xhr_open: function (xhrId, method, url, p_user, p_password) { godot_xhr_open: function (xhrId, method, url, p_user, p_password) {
const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null;
const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null;
GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password);
}, },
godot_xhr_set_request_header__sig: 'viii',
godot_xhr_set_request_header: function (xhrId, header, value) { godot_xhr_set_request_header: function (xhrId, header, value) {
GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value));
}, },
godot_xhr_send_null__sig: 'vi',
godot_xhr_send_null: function (xhrId) { godot_xhr_send_null: function (xhrId) {
GodotHTTPRequest.requests[xhrId].send(); GodotHTTPRequest.requests[xhrId].send();
}, },
godot_xhr_send_string__sig: 'vii',
godot_xhr_send_string: function (xhrId, strPtr) { godot_xhr_send_string: function (xhrId, strPtr) {
if (!strPtr) { if (!strPtr) {
GodotRuntime.error('Failed to send string per XHR: null pointer'); GodotRuntime.error('Failed to send string per XHR: null pointer');
@ -89,6 +96,7 @@ const GodotHTTPRequest = {
GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr)); GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr));
}, },
godot_xhr_send_data__sig: 'viii',
godot_xhr_send_data: function (xhrId, ptr, len) { godot_xhr_send_data: function (xhrId, ptr, len) {
if (!ptr) { if (!ptr) {
GodotRuntime.error('Failed to send data per XHR: null pointer'); GodotRuntime.error('Failed to send data per XHR: null pointer');
@ -101,23 +109,28 @@ const GodotHTTPRequest = {
GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len));
}, },
godot_xhr_abort__sig: 'vi',
godot_xhr_abort: function (xhrId) { godot_xhr_abort: function (xhrId) {
GodotHTTPRequest.requests[xhrId].abort(); GodotHTTPRequest.requests[xhrId].abort();
}, },
godot_xhr_get_status__sig: 'ii',
godot_xhr_get_status: function (xhrId) { godot_xhr_get_status: function (xhrId) {
return GodotHTTPRequest.requests[xhrId].status; return GodotHTTPRequest.requests[xhrId].status;
}, },
godot_xhr_get_ready_state__sig: 'ii',
godot_xhr_get_ready_state: function (xhrId) { godot_xhr_get_ready_state: function (xhrId) {
return GodotHTTPRequest.requests[xhrId].readyState; return GodotHTTPRequest.requests[xhrId].readyState;
}, },
godot_xhr_get_response_headers_length__sig: 'ii',
godot_xhr_get_response_headers_length: function (xhrId) { godot_xhr_get_response_headers_length: function (xhrId) {
const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
return headers === null ? 0 : GodotRuntime.strlen(headers); return headers === null ? 0 : GodotRuntime.strlen(headers);
}, },
godot_xhr_get_response_headers__sig: 'viii',
godot_xhr_get_response_headers: function (xhrId, dst, len) { godot_xhr_get_response_headers: function (xhrId, dst, len) {
const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
if (str === null) { if (str === null) {
@ -126,11 +139,13 @@ const GodotHTTPRequest = {
GodotRuntime.stringToHeap(str, dst, len); GodotRuntime.stringToHeap(str, dst, len);
}, },
godot_xhr_get_response_length__sig: 'ii',
godot_xhr_get_response_length: function (xhrId) { godot_xhr_get_response_length: function (xhrId) {
const body = GodotHTTPRequest.requests[xhrId].response; const body = GodotHTTPRequest.requests[xhrId].response;
return body === null ? 0 : body.byteLength; return body === null ? 0 : body.byteLength;
}, },
godot_xhr_get_response__sig: 'viii',
godot_xhr_get_response: function (xhrId, dst, len) { godot_xhr_get_response: function (xhrId, dst, len) {
let buf = GodotHTTPRequest.requests[xhrId].response; let buf = GodotHTTPRequest.requests[xhrId].response;
if (buf === null) { if (buf === null) {

View File

@ -75,14 +75,17 @@ const GodotConfig = {
}, },
}, },
godot_js_config_canvas_id_get__sig: 'vii',
godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) { godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) {
GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max); GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max);
}, },
godot_js_config_locale_get__sig: 'vii',
godot_js_config_locale_get: function (p_ptr, p_ptr_max) { godot_js_config_locale_get: function (p_ptr, p_ptr_max) {
GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max);
}, },
godot_js_config_is_resize_on_start__sig: 'i',
godot_js_config_is_resize_on_start: function () { godot_js_config_is_resize_on_start: function () {
return GodotConfig.resize_on_start ? 1 : 0; return GodotConfig.resize_on_start ? 1 : 0;
}, },
@ -239,19 +242,23 @@ const GodotOS = {
}, },
}, },
godot_js_os_finish_async__sig: 'vi',
godot_js_os_finish_async: function (p_callback) { godot_js_os_finish_async: function (p_callback) {
const func = GodotRuntime.get_func(p_callback); const func = GodotRuntime.get_func(p_callback);
GodotOS.finish_async(func); GodotOS.finish_async(func);
}, },
godot_js_os_request_quit_cb__sig: 'vi',
godot_js_os_request_quit_cb: function (p_callback) { godot_js_os_request_quit_cb: function (p_callback) {
GodotOS.request_quit = GodotRuntime.get_func(p_callback); GodotOS.request_quit = GodotRuntime.get_func(p_callback);
}, },
godot_js_os_fs_is_persistent__sig: 'i',
godot_js_os_fs_is_persistent: function () { godot_js_os_fs_is_persistent: function () {
return GodotFS.is_persistent(); return GodotFS.is_persistent();
}, },
godot_js_os_fs_sync__sig: 'vi',
godot_js_os_fs_sync: function (callback) { godot_js_os_fs_sync: function (callback) {
const func = GodotRuntime.get_func(callback); const func = GodotRuntime.get_func(callback);
GodotOS._fs_sync_promise = GodotFS.sync(); GodotOS._fs_sync_promise = GodotFS.sync();
@ -260,6 +267,7 @@ const GodotOS = {
}); });
}, },
godot_js_os_execute__sig: 'ii',
godot_js_os_execute: function (p_json) { godot_js_os_execute: function (p_json) {
const json_args = GodotRuntime.parseString(p_json); const json_args = GodotRuntime.parseString(p_json);
const args = JSON.parse(json_args); const args = JSON.parse(json_args);
@ -270,6 +278,7 @@ const GodotOS = {
return 1; return 1;
}, },
godot_js_os_shell_open__sig: 'vi',
godot_js_os_shell_open: function (p_uri) { godot_js_os_shell_open: function (p_uri) {
window.open(GodotRuntime.parseString(p_uri), '_blank'); window.open(GodotRuntime.parseString(p_uri), '_blank');
}, },

View File

@ -43,6 +43,7 @@
#include "modules/websocket/remote_debugger_peer_websocket.h" #include "modules/websocket/remote_debugger_peer_websocket.h"
#endif #endif
#include <dlfcn.h>
#include <emscripten.h> #include <emscripten.h>
#include <stdlib.h> #include <stdlib.h>
@ -127,12 +128,24 @@ int OS_JavaScript::get_process_id() const {
} }
bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "HTML5" || p_feature == "web") if (p_feature == "HTML5" || p_feature == "web") {
return true; return true;
}
#ifdef JAVASCRIPT_EVAL_ENABLED #ifdef JAVASCRIPT_EVAL_ENABLED
if (p_feature == "JavaScript") if (p_feature == "JavaScript") {
return true; return true;
}
#endif
#ifndef NO_THREADS
if (p_feature == "threads") {
return true;
}
#endif
#if WASM_GDNATIVE
if (p_feature == "wasm32") {
return true;
}
#endif #endif
return false; return false;
@ -187,6 +200,13 @@ bool OS_JavaScript::is_userfs_persistent() const {
return idb_available; return idb_available;
} }
Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
String path = p_path.get_file();
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
return OK;
}
OS_JavaScript *OS_JavaScript::get_singleton() { OS_JavaScript *OS_JavaScript::get_singleton() {
return static_cast<OS_JavaScript *>(OS::get_singleton()); return static_cast<OS_JavaScript *>(OS::get_singleton());
} }

View File

@ -87,6 +87,7 @@ public:
String get_user_data_dir() const override; String get_user_data_dir() const override;
bool is_userfs_persistent() const override; bool is_userfs_persistent() const override;
Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override;
void resume_audio(); void resume_audio();