mirror of
https://github.com/godotengine/godot.git
synced 2024-12-14 23:27:24 +00:00
Merge pull request #44170 from Faless/js/3.x_gdnative
[3.2] [HTML5] Optional GDNative Support
This commit is contained in:
commit
2beb36fbad
2
misc/dist/html/full-size.html
vendored
2
misc/dist/html/full-size.html
vendored
@ -145,6 +145,7 @@ $GODOT_HEAD_INCLUDE
|
||||
|
||||
const EXECUTABLE_NAME = '$GODOT_BASENAME';
|
||||
const MAIN_PACK = '$GODOT_BASENAME.pck';
|
||||
const GDNATIVE_LIBS = [$GODOT_GDNATIVE_LIBS];
|
||||
const INDETERMINATE_STATUS_STEP_MS = 100;
|
||||
const FULL_WINDOW = $GODOT_FULL_WINDOW;
|
||||
|
||||
@ -267,6 +268,7 @@ $GODOT_HEAD_INCLUDE
|
||||
} else {
|
||||
setStatusMode('indeterminate');
|
||||
engine.setCanvas(canvas);
|
||||
engine.setGDNativeLibraries(GDNATIVE_LIBS);
|
||||
engine.startGame(EXECUTABLE_NAME, MAIN_PACK).then(() => {
|
||||
setStatusMode('hidden');
|
||||
initializing = false;
|
||||
|
@ -333,11 +333,11 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() {
|
||||
platform_android.library_extension = "*.so";
|
||||
platforms["Android"] = platform_android;
|
||||
|
||||
// TODO: Javascript platform is not supported yet
|
||||
// NativePlatformConfig platform_html5;
|
||||
// platform_html5.name = "HTML5";
|
||||
// platform_html5.library_extension = "*.wasm";
|
||||
// platforms["Javascript"] = platform_html5;
|
||||
NativePlatformConfig platform_html5;
|
||||
platform_html5.name = "HTML5";
|
||||
platform_html5.entries.push_back("wasm32");
|
||||
platform_html5.library_extension = "*.wasm";
|
||||
platforms["HTML5"] = platform_html5;
|
||||
|
||||
NativePlatformConfig platform_ios;
|
||||
platform_ios.name = "iOS";
|
||||
|
@ -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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref) {
|
||||
@ -129,14 +131,17 @@ const GodotRTCDataChannel = {
|
||||
return 0;
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_is_ordered__sig: 'ii',
|
||||
godot_js_rtc_datachannel_is_ordered: function (p_id) {
|
||||
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) {
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref) {
|
||||
@ -151,14 +156,17 @@ const GodotRTCDataChannel = {
|
||||
return 65535;
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii',
|
||||
godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
|
||||
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);
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_label_get__sig: 'ii',
|
||||
godot_js_rtc_datachannel_label_get: function (p_id) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref || !ref.label) {
|
||||
@ -167,6 +175,7 @@ const GodotRTCDataChannel = {
|
||||
return GodotRuntime.allocString(ref.label);
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_protocol_get__sig: 'ii',
|
||||
godot_js_rtc_datachannel_protocol_get: function (p_id) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref || !ref.protocol) {
|
||||
@ -175,11 +184,13 @@ const GodotRTCDataChannel = {
|
||||
return GodotRuntime.allocString(ref.protocol);
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_destroy__sig: 'vi',
|
||||
godot_js_rtc_datachannel_destroy: function (p_id) {
|
||||
GodotRTCDataChannel.close(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) {
|
||||
const onopen = GodotRuntime.get_func(p_on_open).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);
|
||||
},
|
||||
|
||||
godot_js_rtc_datachannel_close__sig: 'vi',
|
||||
godot_js_rtc_datachannel_close: function (p_id) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
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) {
|
||||
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);
|
||||
@ -302,6 +315,7 @@ const GodotRTCPeerConnection = {
|
||||
return id;
|
||||
},
|
||||
|
||||
godot_js_rtc_pc_close__sig: 'vi',
|
||||
godot_js_rtc_pc_close: function (p_id) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref) {
|
||||
@ -310,6 +324,7 @@ const GodotRTCPeerConnection = {
|
||||
ref.close();
|
||||
},
|
||||
|
||||
godot_js_rtc_pc_destroy__sig: 'vi',
|
||||
godot_js_rtc_pc_destroy: function (p_id) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref) {
|
||||
@ -321,6 +336,7 @@ const GodotRTCPeerConnection = {
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
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) {
|
||||
const ref = IDHandler.get(p_id);
|
||||
if (!ref) {
|
||||
@ -390,6 +409,7 @@ const GodotRTCPeerConnection = {
|
||||
},
|
||||
|
||||
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) {
|
||||
try {
|
||||
const ref = IDHandler.get(p_id);
|
||||
|
@ -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) {
|
||||
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);
|
||||
@ -156,6 +157,7 @@ const GodotWebSocket = {
|
||||
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) {
|
||||
const bytes_array = new Uint8Array(p_buf_len);
|
||||
let i = 0;
|
||||
@ -169,12 +171,14 @@ const GodotWebSocket = {
|
||||
return GodotWebSocket.send(p_id, out);
|
||||
},
|
||||
|
||||
godot_js_websocket_close__sig: 'viii',
|
||||
godot_js_websocket_close: function (p_id, p_code, p_reason) {
|
||||
const code = p_code;
|
||||
const reason = GodotRuntime.parseString(p_reason);
|
||||
GodotWebSocket.close(p_id, code, reason);
|
||||
},
|
||||
|
||||
godot_js_websocket_destroy__sig: 'vi',
|
||||
godot_js_websocket_destroy: function (p_id) {
|
||||
GodotWebSocket.destroy(p_id);
|
||||
},
|
||||
|
@ -11,13 +11,8 @@ javascript_files = [
|
||||
"api/javascript_tools_editor_plugin.cpp",
|
||||
]
|
||||
|
||||
build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
|
||||
if env["threads_enabled"]:
|
||||
build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
|
||||
|
||||
build = env.add_program(build_targets, javascript_files)
|
||||
|
||||
env.AddJSLibraries(
|
||||
sys_env = env.Clone()
|
||||
sys_env.AddJSLibraries(
|
||||
[
|
||||
"js/libs/library_godot_audio.js",
|
||||
"js/libs/library_godot_display.js",
|
||||
@ -28,12 +23,48 @@ env.AddJSLibraries(
|
||||
)
|
||||
|
||||
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"]:
|
||||
env.AddJSLibraries(["js/libs/library_godot_eval.js"])
|
||||
for lib in env["JS_LIBS"]:
|
||||
env.Append(LINKFLAGS=["--js-library", lib])
|
||||
env.Depends(build, env["JS_LIBS"])
|
||||
sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"])
|
||||
for lib in sys_env["JS_LIBS"]:
|
||||
sys_env.Append(LINKFLAGS=["--js-library", lib])
|
||||
|
||||
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 = [
|
||||
"js/engine/preloader.js",
|
||||
@ -60,8 +91,11 @@ out_files = [
|
||||
]
|
||||
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"]
|
||||
if env["threads_enabled"]:
|
||||
in_files.append(build[2])
|
||||
if env["gdnative_enabled"]:
|
||||
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"))
|
||||
|
||||
zip_files = env.InstallAs(out_files, in_files)
|
||||
|
@ -23,6 +23,7 @@ def get_opts():
|
||||
# eval() can be a security concern, so it can be disabled.
|
||||
BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
|
||||
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),
|
||||
]
|
||||
|
||||
@ -83,10 +84,8 @@ def configure(env):
|
||||
|
||||
# LTO
|
||||
if env["use_lto"]:
|
||||
env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"])
|
||||
env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"])
|
||||
env.Append(CCFLAGS=["-flto"])
|
||||
env.Append(LINKFLAGS=["-flto"])
|
||||
env.Append(CCFLAGS=["-flto=full"])
|
||||
env.Append(LINKFLAGS=["-flto=full"])
|
||||
|
||||
# Closure compiler
|
||||
if env["use_closure_compiler"]:
|
||||
@ -133,6 +132,9 @@ def configure(env):
|
||||
if env["javascript_eval"]:
|
||||
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).
|
||||
if env["threads_enabled"]:
|
||||
env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
|
||||
@ -144,14 +146,15 @@ def configure(env):
|
||||
else:
|
||||
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).
|
||||
env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
|
||||
|
||||
# We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to
|
||||
# be linked explicitly.
|
||||
env.Append(LIBS=["idbfs.js"])
|
||||
|
||||
env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
|
||||
# Wrap the JavaScript support code around a closure named Godot.
|
||||
env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
|
||||
|
||||
# Allow increasing memory buffer size during runtime. This is efficient
|
||||
@ -162,12 +165,14 @@ def configure(env):
|
||||
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 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"])
|
||||
|
||||
# Allow use to take control of swapping WebGL buffers.
|
||||
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']"])
|
||||
|
||||
# Add code that allow exiting runtime.
|
||||
env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])
|
||||
|
@ -85,36 +85,44 @@ public:
|
||||
// Wrong protocol
|
||||
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";
|
||||
String ctype = "";
|
||||
String filepath;
|
||||
String ctype;
|
||||
if (req[1] == basereq + ".html") {
|
||||
filepath += ".html";
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
ctype = "text/html";
|
||||
} else if (req[1] == basereq + ".js") {
|
||||
filepath += ".js";
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
ctype = "application/javascript";
|
||||
} else if (req[1] == basereq + ".audio.worklet.js") {
|
||||
filepath += ".audio.worklet.js";
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
ctype = "application/javascript";
|
||||
} else if (req[1] == basereq + ".worker.js") {
|
||||
filepath += ".worker.js";
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
ctype = "application/javascript";
|
||||
} else if (req[1] == basereq + ".pck") {
|
||||
filepath += ".pck";
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
ctype = "application/octet-stream";
|
||||
} else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") {
|
||||
// Also allow serving the generated favicon for a smoother loading experience.
|
||||
if (req[1] == "/favicon.png") {
|
||||
filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png");
|
||||
} else {
|
||||
filepath += ".png";
|
||||
filepath = basereq + ".png";
|
||||
}
|
||||
ctype = "image/png";
|
||||
} else if (req[1] == basereq + ".wasm") {
|
||||
filepath += ".wasm";
|
||||
} else if (req[1] == basereq + ".side.wasm") {
|
||||
filepath = cache_path.plus_file(req[1].get_file());
|
||||
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";
|
||||
s += "Connection: Close\r\n";
|
||||
s += "\r\n";
|
||||
@ -204,7 +212,33 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
|
||||
Ref<ImageTexture> stop_icon;
|
||||
int menu_options;
|
||||
|
||||
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug);
|
||||
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, const Vector<SharedObject> p_shared_objects);
|
||||
|
||||
private:
|
||||
Ref<EditorHTTPServer> server;
|
||||
@ -248,11 +282,15 @@ public:
|
||||
~EditorExportPlatformJavaScript();
|
||||
};
|
||||
|
||||
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug) {
|
||||
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, const Vector<SharedObject> p_shared_objects) {
|
||||
|
||||
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
|
||||
String str_export;
|
||||
Vector<String> lines = str_template.split("\n");
|
||||
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++) {
|
||||
|
||||
@ -261,6 +299,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_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_GDNATIVE_LIBS", libs);
|
||||
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
|
||||
str_export += current_line + "\n";
|
||||
}
|
||||
@ -289,15 +328,21 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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::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_mobile"), false)); // ETC or ETC2, depending on renderer
|
||||
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true));
|
||||
@ -322,11 +367,11 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p
|
||||
|
||||
String err;
|
||||
bool valid = false;
|
||||
ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
|
||||
|
||||
// Look for export templates (first official, and if defined custom templates).
|
||||
|
||||
bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err);
|
||||
bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err);
|
||||
bool dvalid = exists_export_template(_get_template_name(mode, true), &err);
|
||||
bool rvalid = exists_export_template(_get_template_name(mode, false), &err);
|
||||
|
||||
if (p_preset->get("custom_template/debug") != "") {
|
||||
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
|
||||
@ -380,10 +425,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||
|
||||
if (template_path == String()) {
|
||||
|
||||
if (p_debug)
|
||||
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG);
|
||||
else
|
||||
template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE);
|
||||
ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
|
||||
template_path = find_export_template(_get_template_name(mode, p_debug));
|
||||
}
|
||||
|
||||
if (!DirAccess::exists(p_path.get_base_dir())) {
|
||||
@ -395,12 +438,24 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||
return ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
Vector<SharedObject> shared_objects;
|
||||
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) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
|
||||
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 = NULL;
|
||||
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
|
||||
@ -441,7 +496,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||
if (!custom_html.empty()) {
|
||||
continue;
|
||||
}
|
||||
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug);
|
||||
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, shared_objects);
|
||||
file = p_path.get_file();
|
||||
|
||||
} else if (file == "godot.js") {
|
||||
@ -451,6 +506,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||
|
||||
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") {
|
||||
|
||||
file = p_path.get_file().get_basename() + ".audio.worklet.js";
|
||||
@ -484,7 +543,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||
buf.resize(f->get_len());
|
||||
f->get_buffer(buf.ptrw(), buf.size());
|
||||
memdelete(f);
|
||||
_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug);
|
||||
_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, shared_objects);
|
||||
|
||||
f = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
if (!f) {
|
||||
@ -592,6 +651,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
|
||||
DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
|
||||
DirAccess::remove_file_or_error(basepath + ".pck");
|
||||
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(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"));
|
||||
return err;
|
||||
|
@ -48,7 +48,7 @@ extern void godot_audio_capture_stop();
|
||||
typedef int32_t GodotAudioState[4];
|
||||
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_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_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout);
|
||||
|
||||
|
@ -47,7 +47,7 @@ typedef enum {
|
||||
|
||||
extern int godot_xhr_new();
|
||||
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 = NULL, const char *p_password = NULL);
|
||||
|
||||
|
@ -74,7 +74,7 @@ void main_loop_callback() {
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
|
||||
// Set locale
|
||||
char locale_ptr[16];
|
||||
godot_js_config_locale_get(locale_ptr, sizeof(locale_ptr));
|
||||
|
35
platform/javascript/javascript_runtime.cpp
Normal file
35
platform/javascript/javascript_runtime.cpp
Normal 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);
|
||||
}
|
1
platform/javascript/js/dynlink.pre.js
Normal file
1
platform/javascript/js/dynlink.pre.js
Normal file
@ -0,0 +1 @@
|
||||
Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []);
|
@ -34,6 +34,7 @@ const Engine = (function () {
|
||||
this.onExecute = null;
|
||||
this.onExit = null;
|
||||
this.persistentPaths = ['/userfs'];
|
||||
this.gdnativeLibs = [];
|
||||
}
|
||||
|
||||
Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
|
||||
@ -58,6 +59,10 @@ const Engine = (function () {
|
||||
initPromise = new Promise(function (resolve, reject) {
|
||||
config['locateFile'] = Utils.createLocateRewrite(loadPath);
|
||||
config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
|
||||
// Emscripten configuration.
|
||||
config['thisProgram'] = me.executableName;
|
||||
config['noExitRuntime'] = true;
|
||||
config['dynamicLibraries'] = me.gdnativeLibs;
|
||||
Godot(config).then(function (module) {
|
||||
module['initFS'](me.persistentPaths).then(function (fs_err) {
|
||||
me.rtenv = module;
|
||||
@ -119,9 +124,6 @@ const Engine = (function () {
|
||||
locale = navigator.languages ? navigator.languages[0] : navigator.language;
|
||||
locale = locale.split('.')[0];
|
||||
}
|
||||
// Emscripten configuration.
|
||||
me.rtenv['thisProgram'] = me.executableName;
|
||||
me.rtenv['noExitRuntime'] = true;
|
||||
// Godot configuration.
|
||||
me.rtenv['initConfig']({
|
||||
'resizeCanvasOnStart': me.resizeCanvasOnStart,
|
||||
@ -249,6 +251,10 @@ const Engine = (function () {
|
||||
this.persistentPaths = persistentPaths;
|
||||
};
|
||||
|
||||
Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) {
|
||||
this.gdnativeLibs = gdnativeLibs;
|
||||
};
|
||||
|
||||
Engine.prototype.requestQuit = function () {
|
||||
if (this.rtenv) {
|
||||
this.rtenv['request_quit']();
|
||||
@ -277,6 +283,7 @@ const Engine = (function () {
|
||||
Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
|
||||
Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
|
||||
Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
|
||||
Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries;
|
||||
Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
|
||||
return Engine;
|
||||
}());
|
||||
|
@ -8,6 +8,8 @@ const Utils = { // eslint-disable-line no-unused-vars
|
||||
return `${execName}.audio.worklet.js`;
|
||||
} else if (path.endsWith('.js')) {
|
||||
return `${execName}.js`;
|
||||
} else if (path.endsWith('.side.wasm')) {
|
||||
return `${execName}.side.wasm`;
|
||||
} else if (path.endsWith('.wasm')) {
|
||||
return `${execName}.wasm`;
|
||||
}
|
||||
|
@ -137,6 +137,7 @@ const GodotAudio = {
|
||||
},
|
||||
},
|
||||
|
||||
godot_audio_is_available__sig: 'i',
|
||||
godot_audio_is_available__proxy: 'sync',
|
||||
godot_audio_is_available: function () {
|
||||
if (!(window.AudioContext || window.webkitAudioContext)) {
|
||||
@ -145,12 +146,14 @@ const GodotAudio = {
|
||||
return 1;
|
||||
},
|
||||
|
||||
godot_audio_init__sig: 'iiiii',
|
||||
godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
|
||||
const statechange = GodotRuntime.get_func(p_state_change);
|
||||
const latencyupdate = GodotRuntime.get_func(p_latency_update);
|
||||
return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
|
||||
},
|
||||
|
||||
godot_audio_resume__sig: 'v',
|
||||
godot_audio_resume: function () {
|
||||
if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
|
||||
GodotAudio.ctx.resume();
|
||||
@ -158,6 +161,7 @@ const GodotAudio = {
|
||||
},
|
||||
|
||||
godot_audio_capture_start__proxy: 'sync',
|
||||
godot_audio_capture_start__sig: 'v',
|
||||
godot_audio_capture_start: function () {
|
||||
if (GodotAudio.input) {
|
||||
return; // Already started.
|
||||
@ -168,6 +172,7 @@ const GodotAudio = {
|
||||
},
|
||||
|
||||
godot_audio_capture_stop__proxy: 'sync',
|
||||
godot_audio_capture_stop__sig: 'v',
|
||||
godot_audio_capture_stop: function () {
|
||||
if (GodotAudio.input) {
|
||||
const tracks = GodotAudio.input['mediaStream']['getTracks']();
|
||||
@ -241,10 +246,12 @@ const GodotAudioWorklet = {
|
||||
},
|
||||
},
|
||||
|
||||
godot_audio_worklet_create__sig: 'vi',
|
||||
godot_audio_worklet_create: function (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) {
|
||||
const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_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);
|
||||
},
|
||||
|
||||
godot_audio_worklet_state_wait__sig: 'iiii',
|
||||
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);
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
const onprocess = GodotRuntime.get_func(p_cb);
|
||||
GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
|
||||
|
@ -280,6 +280,7 @@ const GodotDisplay = {
|
||||
window_icon: '',
|
||||
},
|
||||
|
||||
godot_js_display_is_swap_ok_cancel__sig: 'i',
|
||||
godot_js_display_is_swap_ok_cancel: function () {
|
||||
const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
|
||||
const plat = navigator.platform || '';
|
||||
@ -289,10 +290,12 @@ const GodotDisplay = {
|
||||
return 0;
|
||||
},
|
||||
|
||||
godot_js_display_alert__sig: 'vi',
|
||||
godot_js_display_alert: function (p_text) {
|
||||
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 () {
|
||||
return window.devicePixelRatio || 1;
|
||||
},
|
||||
@ -300,14 +303,17 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Canvas
|
||||
*/
|
||||
godot_js_display_canvas_focus__sig: 'v',
|
||||
godot_js_display_canvas_focus: function () {
|
||||
GodotConfig.canvas.focus();
|
||||
},
|
||||
|
||||
godot_js_display_canvas_is_focused__sig: 'i',
|
||||
godot_js_display_canvas_is_focused: function () {
|
||||
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) {
|
||||
const brect = GodotConfig.canvas.getBoundingClientRect();
|
||||
GodotRuntime.setHeapValue(r_x, brect.x, 'i32');
|
||||
@ -317,6 +323,7 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Touchscreen
|
||||
*/
|
||||
godot_js_display_touchscreen_is_available__sig: 'i',
|
||||
godot_js_display_touchscreen_is_available: function () {
|
||||
return 'ontouchstart' in window;
|
||||
},
|
||||
@ -324,6 +331,7 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Clipboard
|
||||
*/
|
||||
godot_js_display_clipboard_set__sig: 'ii',
|
||||
godot_js_display_clipboard_set: function (p_text) {
|
||||
const text = GodotRuntime.parseString(p_text);
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
@ -336,6 +344,7 @@ const GodotDisplay = {
|
||||
return 0;
|
||||
},
|
||||
|
||||
godot_js_display_clipboard_get__sig: 'ii',
|
||||
godot_js_display_clipboard_get: function (callback) {
|
||||
const func = GodotRuntime.get_func(callback);
|
||||
try {
|
||||
@ -354,6 +363,7 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Window
|
||||
*/
|
||||
godot_js_display_window_request_fullscreen__sig: 'v',
|
||||
godot_js_display_window_request_fullscreen: function () {
|
||||
const canvas = GodotConfig.canvas;
|
||||
(canvas.requestFullscreen || canvas.msRequestFullscreen
|
||||
@ -362,10 +372,12 @@ const GodotDisplay = {
|
||||
).call(canvas);
|
||||
},
|
||||
|
||||
godot_js_display_window_title_set__sig: 'vi',
|
||||
godot_js_display_window_title_set: function (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) {
|
||||
let link = document.getElementById('-gd-engine-icon');
|
||||
if (link === null) {
|
||||
@ -386,6 +398,7 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Cursor
|
||||
*/
|
||||
godot_js_display_cursor_set_visible__sig: 'vi',
|
||||
godot_js_display_cursor_set_visible: function (p_visible) {
|
||||
const visible = p_visible !== 0;
|
||||
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 () {
|
||||
return !GodotDisplayCursor.visible;
|
||||
},
|
||||
|
||||
godot_js_display_cursor_set_shape__sig: 'vi',
|
||||
godot_js_display_cursor_set_shape: function (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) {
|
||||
const shape = GodotRuntime.parseString(p_shape);
|
||||
const old_shape = GodotDisplayCursor.cursors[shape];
|
||||
@ -432,6 +448,7 @@ const GodotDisplay = {
|
||||
/*
|
||||
* Listeners
|
||||
*/
|
||||
godot_js_display_notification_cb__sig: 'viiiii',
|
||||
godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) {
|
||||
const canvas = GodotConfig.canvas;
|
||||
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) {
|
||||
const func = GodotRuntime.get_func(callback);
|
||||
GodotDisplayListeners.add(window, 'paste', function (evt) {
|
||||
@ -453,6 +471,7 @@ const GodotDisplay = {
|
||||
}, false);
|
||||
},
|
||||
|
||||
godot_js_display_drop_files_cb__sig: 'vi',
|
||||
godot_js_display_drop_files_cb: function (callback) {
|
||||
const func = GodotRuntime.get_func(callback);
|
||||
const dropFiles = function (files) {
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
const GodotEditorTools = {
|
||||
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) {
|
||||
const path = GodotRuntime.parseString(p_path);
|
||||
const name = GodotRuntime.parseString(p_name);
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
const GodotEval = {
|
||||
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) {
|
||||
const js_code = GodotRuntime.parseString(p_js);
|
||||
let eval_ret = null;
|
||||
|
@ -50,6 +50,7 @@ const GodotHTTPRequest = {
|
||||
},
|
||||
},
|
||||
|
||||
godot_xhr_new__sig: 'i',
|
||||
godot_xhr_new: function () {
|
||||
const newId = GodotHTTPRequest.getUnusedRequestId();
|
||||
GodotHTTPRequest.requests[newId] = new XMLHttpRequest();
|
||||
@ -57,30 +58,36 @@ const GodotHTTPRequest = {
|
||||
return newId;
|
||||
},
|
||||
|
||||
godot_xhr_reset__sig: 'vi',
|
||||
godot_xhr_reset: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest();
|
||||
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]);
|
||||
},
|
||||
|
||||
godot_xhr_free__sig: 'vi',
|
||||
godot_xhr_free: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId].abort();
|
||||
GodotHTTPRequest.requests[xhrId] = null;
|
||||
},
|
||||
|
||||
godot_xhr_open__sig: 'viiiii',
|
||||
godot_xhr_open: function (xhrId, method, url, p_user, p_password) {
|
||||
const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null;
|
||||
const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null;
|
||||
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) {
|
||||
GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value));
|
||||
},
|
||||
|
||||
godot_xhr_send_null__sig: 'vi',
|
||||
godot_xhr_send_null: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId].send();
|
||||
},
|
||||
|
||||
godot_xhr_send_string__sig: 'vii',
|
||||
godot_xhr_send_string: function (xhrId, strPtr) {
|
||||
if (!strPtr) {
|
||||
GodotRuntime.error('Failed to send string per XHR: null pointer');
|
||||
@ -89,6 +96,7 @@ const GodotHTTPRequest = {
|
||||
GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr));
|
||||
},
|
||||
|
||||
godot_xhr_send_data__sig: 'viii',
|
||||
godot_xhr_send_data: function (xhrId, ptr, len) {
|
||||
if (!ptr) {
|
||||
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));
|
||||
},
|
||||
|
||||
godot_xhr_abort__sig: 'vi',
|
||||
godot_xhr_abort: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId].abort();
|
||||
},
|
||||
|
||||
godot_xhr_get_status__sig: 'ii',
|
||||
godot_xhr_get_status: function (xhrId) {
|
||||
return GodotHTTPRequest.requests[xhrId].status;
|
||||
},
|
||||
|
||||
godot_xhr_get_ready_state__sig: 'ii',
|
||||
godot_xhr_get_ready_state: function (xhrId) {
|
||||
return GodotHTTPRequest.requests[xhrId].readyState;
|
||||
},
|
||||
|
||||
godot_xhr_get_response_headers_length__sig: 'ii',
|
||||
godot_xhr_get_response_headers_length: function (xhrId) {
|
||||
const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
|
||||
return headers === null ? 0 : GodotRuntime.strlen(headers);
|
||||
},
|
||||
|
||||
godot_xhr_get_response_headers__sig: 'viii',
|
||||
godot_xhr_get_response_headers: function (xhrId, dst, len) {
|
||||
const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
|
||||
if (str === null) {
|
||||
@ -126,11 +139,13 @@ const GodotHTTPRequest = {
|
||||
GodotRuntime.stringToHeap(str, dst, len);
|
||||
},
|
||||
|
||||
godot_xhr_get_response_length__sig: 'ii',
|
||||
godot_xhr_get_response_length: function (xhrId) {
|
||||
const body = GodotHTTPRequest.requests[xhrId].response;
|
||||
return body === null ? 0 : body.byteLength;
|
||||
},
|
||||
|
||||
godot_xhr_get_response__sig: 'viii',
|
||||
godot_xhr_get_response: function (xhrId, dst, len) {
|
||||
let buf = GodotHTTPRequest.requests[xhrId].response;
|
||||
if (buf === null) {
|
||||
|
@ -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) {
|
||||
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) {
|
||||
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 () {
|
||||
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) {
|
||||
const func = GodotRuntime.get_func(p_callback);
|
||||
GodotOS.finish_async(func);
|
||||
},
|
||||
|
||||
godot_js_os_request_quit_cb__sig: 'vi',
|
||||
godot_js_os_request_quit_cb: function (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 () {
|
||||
return GodotFS.is_persistent();
|
||||
},
|
||||
|
||||
godot_js_os_fs_sync__sig: 'vi',
|
||||
godot_js_os_fs_sync: function (callback) {
|
||||
const func = GodotRuntime.get_func(callback);
|
||||
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) {
|
||||
const json_args = GodotRuntime.parseString(p_json);
|
||||
const args = JSON.parse(json_args);
|
||||
@ -270,6 +278,7 @@ const GodotOS = {
|
||||
return 1;
|
||||
},
|
||||
|
||||
godot_js_os_shell_open__sig: 'vi',
|
||||
godot_js_os_shell_open: function (p_uri) {
|
||||
window.open(GodotRuntime.parseString(p_uri), '_blank');
|
||||
},
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "servers/visual/visual_server_wrap_mt.h"
|
||||
#endif
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <emscripten.h>
|
||||
#include <png.h>
|
||||
#include <stdlib.h>
|
||||
@ -1070,12 +1071,24 @@ int OS_JavaScript::get_process_id() const {
|
||||
|
||||
bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
|
||||
|
||||
if (p_feature == "HTML5" || p_feature == "web")
|
||||
if (p_feature == "HTML5" || p_feature == "web") {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef JAVASCRIPT_EVAL_ENABLED
|
||||
if (p_feature == "JavaScript")
|
||||
if (p_feature == "JavaScript") {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifndef NO_THREADS
|
||||
if (p_feature == "threads") {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if WASM_GDNATIVE
|
||||
if (p_feature == "wasm32") {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
@ -1206,6 +1219,13 @@ bool OS_JavaScript::is_userfs_persistent() const {
|
||||
return idb_available;
|
||||
}
|
||||
|
||||
Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
|
||||
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() {
|
||||
|
||||
return static_cast<OS_JavaScript *>(OS::get_singleton());
|
||||
|
@ -185,7 +185,7 @@ public:
|
||||
virtual int get_power_percent_left();
|
||||
|
||||
virtual bool is_userfs_persistent() const;
|
||||
|
||||
Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path);
|
||||
OS_JavaScript();
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user