diff options
Diffstat (limited to 'platform/javascript')
19 files changed, 815 insertions, 283 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index a760e36982..62a8660ae4 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -6,7 +6,7 @@ javascript_files = [ "audio_driver_javascript.cpp", "display_server_javascript.cpp", "http_client_javascript.cpp", - "javascript_eval.cpp", + "javascript_singleton.cpp", "javascript_main.cpp", "os_javascript.cpp", "api/javascript_tools_editor_plugin.cpp", @@ -23,10 +23,8 @@ sys_env.AddJSLibraries( ] ) -if env["tools"]: - sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"]) if env["javascript_eval"]: - sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"]) + sys_env.AddJSLibraries(["js/libs/library_godot_javascript_singleton.js"]) for lib in sys_env["JS_LIBS"]: sys_env.Append(LINKFLAGS=["--js-library", lib]) @@ -47,6 +45,7 @@ if env["gdnative_enabled"]: sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"]) sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"]) + sys_env.Append(LINKFLAGS=["-s", "WARN_ON_UNDEFINED_SYMBOLS=0"]) # 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. diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 2f7bde065f..5ad2bf56cf 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -30,13 +30,14 @@ #include "api.h" #include "core/config/engine.h" -#include "javascript_eval.h" +#include "javascript_singleton.h" #include "javascript_tools_editor_plugin.h" static JavaScript *javascript_eval; void register_javascript_api() { JavaScriptToolsEditorPlugin::initialize(); + ClassDB::register_virtual_class<JavaScriptObject>(); ClassDB::register_virtual_class<JavaScript>(); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); @@ -61,10 +62,46 @@ JavaScript::~JavaScript() {} void JavaScript::_bind_methods() { ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_interface", "interface"), &JavaScript::get_interface); + ClassDB::bind_method(D_METHOD("create_callback", "callable"), &JavaScript::create_callback); + { + MethodInfo mi; + mi.name = "create_object"; + mi.arguments.push_back(PropertyInfo(Variant::STRING, "object")); + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "create_object", &JavaScript::_create_object_bind, mi); + } + ClassDB::bind_method(D_METHOD("download_buffer", "buffer", "name", "mime"), &JavaScript::download_buffer, DEFVAL("application/octet-stream")); } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { return Variant(); } + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + return Ref<JavaScriptObject>(); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + return Ref<JavaScriptObject>(); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + return Ref<JavaScriptObject>(); +} +#endif +#if !defined(JAVASCRIPT_ENABLED) +void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { +} #endif diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_singleton.h index 24f7648ed9..1615efa87e 100644 --- a/platform/javascript/api/javascript_eval.h +++ b/platform/javascript/api/javascript_singleton.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* javascript_eval.h */ +/* javascript_singleton.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,10 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef JAVASCRIPT_EVAL_H -#define JAVASCRIPT_EVAL_H +#ifndef JAVASCRIPT_SINGLETON_H +#define JAVASCRIPT_SINGLETON_H #include "core/object/class_db.h" +#include "core/object/reference.h" + +class JavaScriptObject : public Reference { +private: + GDCLASS(JavaScriptObject, Reference); + +protected: + virtual bool _set(const StringName &p_name, const Variant &p_value) { return false; } + virtual bool _get(const StringName &p_name, Variant &r_ret) const { return false; } + virtual void _get_property_list(List<PropertyInfo> *p_list) const {} +}; class JavaScript : public Object { private: @@ -44,10 +55,14 @@ protected: public: Variant eval(const String &p_code, bool p_use_global_exec_context = false); + Ref<JavaScriptObject> get_interface(const String &p_interface); + Ref<JavaScriptObject> create_callback(const Callable &p_callable); + Variant _create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime = "application/octet-stream"); static JavaScript *get_singleton(); JavaScript(); ~JavaScript(); }; -#endif // JAVASCRIPT_EVAL_H +#endif // JAVASCRIPT_SINGLETON_H diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 7a2c2b2335..b35ccd087f 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -41,7 +41,7 @@ // JavaScript functions defined in library_godot_editor_tools.js extern "C" { -extern void godot_js_editor_download_file(const char *p_path, const char *p_name, const char *p_mime); +extern int godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); } static void _javascript_editor_init_callback() { @@ -69,7 +69,12 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; _zip_recursive(resource_path, base_path, zip); zipClose(zip, nullptr); - godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip"); + FileAccess *f = FileAccess::open("/tmp/project.zip", FileAccess::READ); + ERR_FAIL_COND_MSG(!f, "Unable to create zip file"); + Vector<uint8_t> buf; + buf.resize(f->get_length()); + f->get_buffer(buf.ptrw(), buf.size()); + godot_js_os_download_buffer(buf.ptr(), buf.size(), "project.zip", "application/zip"); } void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { @@ -79,7 +84,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z return; } Vector<uint8_t> data; - int len = f->get_len(); + uint64_t len = f->get_length(); data.resize(len); f->get_buffer(data.ptrw(), len); f->close(); diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index d01e8a8bd4..da6adc4cd8 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -53,6 +53,7 @@ def get_flags(): # in this platform. For the available networking methods, the browser # manages TLS. ("module_mbedtls_enabled", False), + ("vulkan", False), ] diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 234e42376d..cce1f8dca7 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -30,8 +30,8 @@ #include "platform/javascript/display_server_javascript.h" -#include "drivers/dummy/rasterizer_dummy.h" #include "platform/javascript/os_javascript.h" +#include "servers/rendering/rasterizer_dummy.h" #include <emscripten.h> #include <png.h> @@ -120,10 +120,10 @@ void DisplayServerJavaScript::request_quit_callback() { template <typename T> void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { - godot_event->set_shift(emscripten_event_ptr->shiftKey); - godot_event->set_alt(emscripten_event_ptr->altKey); - godot_event->set_control(emscripten_event_ptr->ctrlKey); - godot_event->set_metakey(emscripten_event_ptr->metaKey); + godot_event->set_shift_pressed(emscripten_event_ptr->shiftKey); + godot_event->set_alt_pressed(emscripten_event_ptr->altKey); + godot_event->set_ctrl_pressed(emscripten_event_ptr->ctrlKey); + godot_event->set_meta_pressed(emscripten_event_ptr->metaKey); } Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { @@ -455,10 +455,10 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript ev->set_position(input->get_mouse_position()); ev->set_global_position(ev->get_position()); - ev->set_shift(input->is_key_pressed(KEY_SHIFT)); - ev->set_alt(input->is_key_pressed(KEY_ALT)); - ev->set_control(input->is_key_pressed(KEY_CONTROL)); - ev->set_metakey(input->is_key_pressed(KEY_META)); + ev->set_shift_pressed(input->is_key_pressed(KEY_SHIFT)); + ev->set_alt_pressed(input->is_key_pressed(KEY_ALT)); + ev->set_ctrl_pressed(input->is_key_pressed(KEY_CTRL)); + ev->set_meta_pressed(input->is_key_pressed(KEY_META)); if (p_event->deltaY < 0) ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); @@ -536,7 +536,7 @@ bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { return godot_js_display_touchscreen_is_available(); } -// Virtual Keybaord +// Virtual Keyboard void DisplayServerJavaScript::vk_input_text_callback(const char *p_text, int p_cursor) { DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); if (!ds || ds->input_text_callback.is_null()) { @@ -827,7 +827,6 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const { //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: //case FEATURE_NATIVE_ICON: - //case FEATURE_NATIVE_VIDEO: //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: //case FEATURE_ORIENTATION: diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc index 7902efafe0..69340ff58c 100644 --- a/platform/javascript/dom_keys.inc +++ b/platform/javascript/dom_keys.inc @@ -159,8 +159,8 @@ int dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b DOM2GODOT("Backspace", BACKSPACE); DOM2GODOT("CapsLock", CAPSLOCK); DOM2GODOT("ContextMenu", MENU); - DOM2GODOT("ControlLeft", CONTROL); - DOM2GODOT("ControlRight", CONTROL); + DOM2GODOT("ControlLeft", CTRL); + DOM2GODOT("ControlRight", CTRL); DOM2GODOT("Enter", ENTER); DOM2GODOT("MetaLeft", SUPER_L); DOM2GODOT("MetaRight", SUPER_R); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 14e279b45b..8ce294f31b 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -41,7 +41,7 @@ class EditorHTTPServer : public Reference { private: - Ref<TCP_Server> server; + Ref<TCPServer> server; Map<String, String> mimes; Ref<StreamPeerTCP> tcp; Ref<StreamPeerSSL> ssl; @@ -63,7 +63,7 @@ private: } void _set_internal_certs(Ref<Crypto> p_crypto) { - const String cache_path = EditorSettings::get_singleton()->get_cache_dir(); + const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); const String key_path = cache_path.plus_file("html5_server.key"); const String crt_path = cache_path.plus_file("html5_server.crt"); bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); @@ -100,7 +100,7 @@ public: _clear_client(); } - Error listen(int p_port, IP_Address p_address, bool p_use_ssl, String p_ssl_key, String p_ssl_cert) { + Error listen(int p_port, IPAddress p_address, bool p_use_ssl, String p_ssl_key, String p_ssl_cert) { use_ssl = p_use_ssl; if (use_ssl) { Ref<Crypto> crypto = Crypto::create(); @@ -138,7 +138,7 @@ public: const String req_file = req[1].get_file(); const String req_ext = req[1].get_extension(); - const String cache_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("web"); + const String cache_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("web"); const String filepath = cache_path.plus_file(req_file); if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { @@ -170,8 +170,8 @@ public: while (true) { uint8_t bytes[4096]; - int read = f->get_buffer(bytes, 4096); - if (read < 1) { + uint64_t read = f->get_buffer(bytes, 4096); + if (read == 0) { break; } err = peer->put_data(bytes, read); @@ -547,7 +547,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path); return ERR_FILE_CANT_READ; } - sw.resize(f->get_len()); + sw.resize(f->get_length()); f->get_buffer(sw.ptrw(), sw.size()); memdelete(f); f = nullptr; @@ -651,7 +651,7 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/enabled"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/offline_page", PROPERTY_HINT_FILE, "*.html"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/display", PROPERTY_HINT_ENUM, "Fullscreen,Standalone,Minimal Ui,Browser"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/display", PROPERTY_HINT_ENUM, "Fullscreen,Standalone,Minimal UI,Browser"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/orientation", PROPERTY_HINT_ENUM, "Any,Landscape,Portrait"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_144x144", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.svgz"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/icon_180x180", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg,*.svgz"), "")); @@ -781,13 +781,13 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese FileAccess *f = nullptr; f = FileAccess::open(pck_path, FileAccess::READ); if (f) { - file_sizes[pck_path.get_file()] = (uint64_t)f->get_len(); + file_sizes[pck_path.get_file()] = (uint64_t)f->get_length(); memdelete(f); f = nullptr; } f = FileAccess::open(base_path + ".wasm", FileAccess::READ); if (f) { - file_sizes[base_name + ".wasm"] = (uint64_t)f->get_len(); + file_sizes[base_name + ".wasm"] = (uint64_t)f->get_length(); memdelete(f); f = nullptr; } @@ -800,7 +800,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path); return ERR_FILE_CANT_READ; } - html.resize(f->get_len()); + html.resize(f->get_length()); f->get_buffer(html.ptrw(), html.size()); memdelete(f); f = nullptr; @@ -888,7 +888,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese return OK; } - const String dest = EditorSettings::get_singleton()->get_cache_dir().plus_file("web"); + const String dest = EditorPaths::get_singleton()->get_cache_dir().plus_file("web"); DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da->dir_exists(dest)) { Error err = da->make_dir_recursive(dest); @@ -919,7 +919,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese const uint16_t bind_port = EDITOR_GET("export/web/http_port"); // Resolve host if needed. const String bind_host = EDITOR_GET("export/web/http_host"); - IP_Address bind_ip; + IPAddress bind_ip; if (bind_host.is_valid_ip_address()) { bind_ip = bind_host; } else { diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index 842a93fcba..6544d41c98 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -34,7 +34,8 @@ Error make_request(Method p_method, const String &p_url, const Vector<String> &p static void _parse_headers(int p_len, const char **p_headers, void *p_ref); int js_id = 0; -int read_limit = 4096; +// 64 KiB by default (favors fast download speeds at the cost of memory usage). +int read_limit = 65536; Status status = STATUS_DISCONNECTED; String host; diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp deleted file mode 100644 index cb19dd20d4..0000000000 --- a/platform/javascript/javascript_eval.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* javascript_eval.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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. */ -/*************************************************************************/ - -#ifdef JAVASCRIPT_EVAL_ENABLED - -#include "api/javascript_eval.h" -#include "emscripten.h" - -extern "C" { -union js_eval_ret { - uint32_t b; - double d; - char *s; -}; - -extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); -} - -void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { - PackedByteArray *arr = (PackedByteArray *)p_arr; - VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; - arr->resize(p_len); - *write = arr->write; - return arr->ptrw(); -} - -Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { - union js_eval_ret js_data; - PackedByteArray arr; - VectorWriteProxy<uint8_t> arr_write; - - Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); - - switch (return_type) { - case Variant::BOOL: - return js_data.b; - case Variant::FLOAT: - return js_data.d; - case Variant::STRING: { - String str = String::utf8(js_data.s); - free(js_data.s); // Must free the string allocated in JS. - return str; - } - case Variant::PACKED_BYTE_ARRAY: - arr_write = VectorWriteProxy<uint8_t>(); - return arr; - default: - return Variant(); - } -} - -#endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 0fe95b0a8f..40771d1882 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -66,6 +66,11 @@ void main_loop_callback() { int target_fps = Engine::get_singleton()->get_target_fps(); if (target_fps > 0) { + if (current_ticks - target_ticks > 1000000) { + // When the window loses focus, we stop getting updates and accumulate delay. + // For this reason, if the difference is too big, we reset target ticks to the current ticks. + target_ticks = current_ticks; + } target_ticks += (uint64_t)(1000000 / target_fps); } if (os->main_loop_iterate()) { diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp new file mode 100644 index 0000000000..5ef67c0cdd --- /dev/null +++ b/platform/javascript/javascript_singleton.cpp @@ -0,0 +1,343 @@ +/*************************************************************************/ +/* javascript_singleton.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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. */ +/*************************************************************************/ + +#ifdef JAVASCRIPT_EVAL_ENABLED + +#include "api/javascript_singleton.h" +#include "emscripten.h" + +extern "C" { +typedef union { + int64_t i; + double r; + void *p; +} godot_js_wrapper_ex; + +typedef int (*GodotJSWrapperVariant2JSCallback)(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); +typedef void (*GodotJSWrapperFreeLockCallback)(void **p_lock, int p_type); +extern int godot_js_wrapper_interface_get(const char *p_name); +extern int godot_js_wrapper_object_call(int p_id, const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +extern int godot_js_wrapper_object_get(int p_id, godot_js_wrapper_ex *p_val, const char *p_prop); +extern int godot_js_wrapper_object_getvar(int p_id, int p_type, godot_js_wrapper_ex *p_val); +extern int godot_js_wrapper_object_setvar(int p_id, int p_key_type, godot_js_wrapper_ex *p_key_ex, int p_val_type, godot_js_wrapper_ex *p_val_ex); +extern void godot_js_wrapper_object_set(int p_id, const char *p_name, int p_type, godot_js_wrapper_ex *p_val); +extern void godot_js_wrapper_object_unref(int p_id); +extern int godot_js_wrapper_create_cb(void *p_ref, void (*p_callback)(void *p_ref, int p_arg_id, int p_argc)); +extern int godot_js_wrapper_create_object(const char *p_method, void **p_args, int p_argc, GodotJSWrapperVariant2JSCallback p_variant2js_callback, godot_js_wrapper_ex *p_cb_rval, void **p_lock, GodotJSWrapperFreeLockCallback p_lock_callback); +}; + +class JavaScriptObjectImpl : public JavaScriptObject { +private: + friend class JavaScript; + + int _js_id = 0; + Callable _callable; + + static int _variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock); + static void _free_lock(void **p_lock, int p_type); + static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val); + static void *_alloc_variants(int p_size); + static void _callback(void *p_ref, int p_arg_id, int p_argc); + +protected: + bool _set(const StringName &p_name, const Variant &p_value) override; + bool _get(const StringName &p_name, Variant &r_ret) const override; + void _get_property_list(List<PropertyInfo> *p_list) const override; + +public: + Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override; + void setvar(const Variant &p_key, const Variant &p_value, bool *r_valid = nullptr) override; + Variant call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) override; + JavaScriptObjectImpl() {} + JavaScriptObjectImpl(int p_id) { _js_id = p_id; } + ~JavaScriptObjectImpl() { + if (_js_id) { + godot_js_wrapper_object_unref(_js_id); + } + } +}; + +bool JavaScriptObjectImpl::_set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_value; + int type = _variant2js((const void **)&v, 0, &exchange, &lock); + godot_js_wrapper_object_set(_js_id, name.utf8().get_data(), type, &exchange); + if (lock) { + _free_lock(&lock, type); + } + return true; +} + +bool JavaScriptObjectImpl::_get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V_MSG(!_js_id, false, "Invalid JS instance"); + const String name = p_name; + godot_js_wrapper_ex exchange; + int type = godot_js_wrapper_object_get(_js_id, &exchange, name.utf8().get_data()); + r_ret = _js2variant(type, &exchange); + return true; +} + +Variant JavaScriptObjectImpl::getvar(const Variant &p_key, bool *r_valid) const { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex exchange; + void *lock = nullptr; + const Variant *v = &p_key; + int prop_type = _variant2js((const void **)&v, 0, &exchange, &lock); + int type = godot_js_wrapper_object_getvar(_js_id, prop_type, &exchange); + if (lock) { + _free_lock(&lock, prop_type); + } + if (type < 0) { + return Variant(); + } + if (r_valid) { + *r_valid = true; + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + godot_js_wrapper_ex kex, vex; + void *klock = nullptr; + void *vlock = nullptr; + const Variant *kv = &p_key; + const Variant *vv = &p_value; + int ktype = _variant2js((const void **)&kv, 0, &kex, &klock); + int vtype = _variant2js((const void **)&vv, 0, &vex, &vlock); + int ret = godot_js_wrapper_object_setvar(_js_id, ktype, &kex, vtype, &vex); + if (klock) { + _free_lock(&klock, ktype); + } + if (vlock) { + _free_lock(&vlock, vtype); + } + if (ret == 0 && r_valid) { + *r_valid = true; + } +} + +void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const { +} + +void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) { + ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!"); + const Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::STRING: { + CharString *cs = (CharString *)(*p_lock); + memdelete(cs); + *p_lock = nullptr; + } break; + default: + ERR_FAIL_MSG("Unknown lock type to free. Likely a bug."); + } +} + +Variant JavaScriptObjectImpl::_js2variant(int p_type, godot_js_wrapper_ex *p_val) { + Variant::Type type = (Variant::Type)p_type; + switch (type) { + case Variant::BOOL: + return Variant((bool)p_val->i); + case Variant::INT: + return p_val->i; + case Variant::FLOAT: + return p_val->r; + case Variant::STRING: { + String out((const char *)p_val->p); + free(p_val->p); + return out; + } + case Variant::OBJECT: { + return memnew(JavaScriptObjectImpl(p_val->i)); + } + default: + return Variant(); + } +} + +int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_wrapper_ex *r_val, void **p_lock) { + const Variant **args = (const Variant **)p_args; + const Variant *v = args[p_pos]; + Variant::Type type = v->get_type(); + switch (type) { + case Variant::BOOL: + r_val->i = v->operator bool() ? 1 : 0; + break; + case Variant::INT: { + const int64_t tmp = v->operator int64_t(); + if (tmp >= 1 << 31) { + r_val->r = (double)tmp; + return Variant::FLOAT; + } + r_val->i = v->operator int64_t(); + } break; + case Variant::FLOAT: + r_val->r = v->operator real_t(); + break; + case Variant::STRING: { + CharString *cs = memnew(CharString(v->operator String().utf8())); + r_val->p = (void *)cs->get_data(); + *p_lock = (void *)cs; + } break; + case Variant::OBJECT: { + JavaScriptObject *js_obj = Object::cast_to<JavaScriptObject>(v->operator Object *()); + r_val->i = js_obj != nullptr ? ((JavaScriptObjectImpl *)js_obj)->_js_id : 0; + } break; + default: + break; + } + return type; +} + +Variant JavaScriptObjectImpl::call(const StringName &p_method, const Variant **p_args, int p_argc, Callable::CallError &r_error) { + godot_js_wrapper_ex exchange; + const String method = p_method; + void *lock = nullptr; + const int type = godot_js_wrapper_object_call(_js_id, method.utf8().get_data(), (void **)p_args, p_argc, &_variant2js, &exchange, &lock, &_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Variant(); + } + return _js2variant(type, &exchange); +} + +void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) { + const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref; + ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed."); + Vector<const Variant *> argp; + Array arg_arr; + for (int i = 0; i < p_argc; i++) { + godot_js_wrapper_ex exchange; + exchange.i = i; + int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange); + arg_arr.push_back(_js2variant(type, &exchange)); + } + Variant arg = arg_arr; + const Variant *argv[1] = { &arg }; + Callable::CallError err; + Variant ret; + obj->_callable.call(argv, 1, ret, err); +} + +Ref<JavaScriptObject> JavaScript::create_callback(const Callable &p_callable) { + Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl); + out->_callable = p_callable; + out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback); + return out; +} + +Ref<JavaScriptObject> JavaScript::get_interface(const String &p_interface) { + int js_id = godot_js_wrapper_interface_get(p_interface.utf8().get_data()); + ERR_FAIL_COND_V_MSG(!js_id, Ref<JavaScriptObject>(), "No interface '" + p_interface + "' registered."); + return Ref<JavaScriptObject>(memnew(JavaScriptObjectImpl(js_id))); +} + +Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_argcount < 1) { + r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Ref<JavaScriptObject>(); + } + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Ref<JavaScriptObject>(); + } + godot_js_wrapper_ex exchange; + const String object = *p_args[0]; + void *lock = nullptr; + const Variant **args = p_argcount > 1 ? &p_args[1] : nullptr; + const int type = godot_js_wrapper_create_object(object.utf8().get_data(), (void **)args, p_argcount - 1, &JavaScriptObjectImpl::_variant2js, &exchange, &lock, &JavaScriptObjectImpl::_free_lock); + r_error.error = Callable::CallError::CALL_OK; + if (type < 0) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Ref<JavaScriptObject>(); + } + return JavaScriptObjectImpl::_js2variant(type, &exchange); +} + +extern "C" { +union js_eval_ret { + uint32_t b; + double d; + char *s; +}; + +extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len)); +extern int godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); +} + +void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) { + PackedByteArray *arr = (PackedByteArray *)p_arr; + VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write; + arr->resize(p_len); + *write = arr->write; + return arr->ptrw(); +} + +Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { + union js_eval_ret js_data; + PackedByteArray arr; + VectorWriteProxy<uint8_t> arr_write; + + Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write)); + + switch (return_type) { + case Variant::BOOL: + return js_data.b; + case Variant::FLOAT: + return js_data.d; + case Variant::STRING: { + String str = String::utf8(js_data.s); + free(js_data.s); // Must free the string allocated in JS. + return str; + } + case Variant::PACKED_BYTE_ARRAY: + arr_write = VectorWriteProxy<uint8_t>(); + return arr; + default: + return Variant(); + } +} +#endif // JAVASCRIPT_EVAL_ENABLED + +void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { + godot_js_os_download_buffer(p_arr.ptr(), p_arr.size(), p_name.utf8().get_data(), p_mime.utf8().get_data()); +} diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index ac4055516c..45c3a3fe2e 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -59,7 +59,7 @@ const GodotAudio = { } onstatechange(state); }; - ctx.onstatechange(); // Immeditately notify state. + ctx.onstatechange(); // Immediately notify state. // Update computed latency GodotAudio.interval = setInterval(function () { let computed_latency = 0; diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js deleted file mode 100644 index d7f1ad5ea1..0000000000 --- a/platform/javascript/js/libs/library_godot_editor_tools.js +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************/ -/* library_godot_editor_tools.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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. */ -/*************************************************************************/ - -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); - const mime = GodotRuntime.parseString(p_mime); - const size = FS.stat(path)['size']; - const buf = new Uint8Array(size); - const fd = FS.open(path, 'r'); - FS.read(fd, buf, 0, size); - FS.close(fd); - FS.unlink(path); - const blob = new Blob([buf], { type: mime }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = name; - a.style.display = 'none'; - document.body.appendChild(a); - a.click(); - a.remove(); - window.URL.revokeObjectURL(url); - }, -}; - -mergeInto(LibraryManager.library, GodotEditorTools); diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js deleted file mode 100644 index 9ab392b813..0000000000 --- a/platform/javascript/js/libs/library_godot_eval.js +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* library_godot_eval.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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. */ -/*************************************************************************/ - -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; - try { - if (p_use_global_ctx) { - // indirect eval call grants global execution context - const global_eval = eval; // eslint-disable-line no-eval - eval_ret = global_eval(js_code); - } else { - eval_ret = eval(js_code); // eslint-disable-line no-eval - } - } catch (e) { - GodotRuntime.error(e); - } - - switch (typeof eval_ret) { - case 'boolean': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); - return 1; // BOOL - - case 'number': - GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); - return 3; // REAL - - case 'string': - GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); - return 4; // STRING - - case 'object': - if (eval_ret === null) { - break; - } - - if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { - eval_ret = new Uint8Array(eval_ret.buffer); - } else if (eval_ret instanceof ArrayBuffer) { - eval_ret = new Uint8Array(eval_ret); - } - if (eval_ret instanceof Uint8Array) { - const func = GodotRuntime.get_func(p_callback); - const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); - HEAPU8.set(eval_ret, bytes_ptr); - return 20; // POOL_BYTE_ARRAY - } - break; - - // no default - } - return 0; // NIL - }, -}; - -mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_javascript_singleton.js b/platform/javascript/js/libs/library_godot_javascript_singleton.js new file mode 100644 index 0000000000..09ef4a1a5d --- /dev/null +++ b/platform/javascript/js/libs/library_godot_javascript_singleton.js @@ -0,0 +1,333 @@ +/*************************************************************************/ +/* library_godot_eval.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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. */ +/*************************************************************************/ + +const GodotJSWrapper = { + + $GodotJSWrapper__deps: ['$GodotRuntime', '$IDHandler'], + $GodotJSWrapper__postset: 'GodotJSWrapper.proxies = new Map();', + $GodotJSWrapper: { + proxies: null, + + MyProxy: function (val) { + const id = IDHandler.add(this); + GodotJSWrapper.proxies.set(val, id); + let refs = 1; + this.ref = function () { + refs++; + }; + this.unref = function () { + refs--; + if (refs === 0) { + IDHandler.remove(id); + GodotJSWrapper.proxies.delete(val); + } + }; + this.get_val = function () { + return val; + }; + this.get_id = function () { + return id; + }; + }, + + get_proxied: function (val) { + const id = GodotJSWrapper.proxies.get(val); + if (id === undefined) { + const proxy = new GodotJSWrapper.MyProxy(val); + return proxy.get_id(); + } + IDHandler.get(id).ref(); + return id; + }, + + get_proxied_value: function (id) { + const proxy = IDHandler.get(id); + if (proxy === undefined) { + return undefined; + } + return proxy.get_val(); + }, + + variant2js: function (type, val) { + switch (type) { + case 0: + return null; + case 1: + return !!GodotRuntime.getHeapValue(val, 'i64'); + case 2: + return GodotRuntime.getHeapValue(val, 'i64'); + case 3: + return GodotRuntime.getHeapValue(val, 'double'); + case 4: + return GodotRuntime.parseString(GodotRuntime.getHeapValue(val, '*')); + case 21: // OBJECT + return GodotJSWrapper.get_proxied_value(GodotRuntime.getHeapValue(val, 'i64')); + default: + return undefined; + } + }, + + js2variant: function (p_val, p_exchange) { + if (p_val === undefined || p_val === null) { + return 0; // NIL + } + const type = typeof (p_val); + if (type === 'boolean') { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 1; // BOOL + } else if (type === 'number') { + if (Number.isInteger(p_val)) { + GodotRuntime.setHeapValue(p_exchange, p_val, 'i64'); + return 2; // INT + } + GodotRuntime.setHeapValue(p_exchange, p_val, 'double'); + return 3; // REAL + } else if (type === 'string') { + const c_str = GodotRuntime.allocString(p_val); + GodotRuntime.setHeapValue(p_exchange, c_str, '*'); + return 4; // STRING + } + const id = GodotJSWrapper.get_proxied(p_val); + GodotRuntime.setHeapValue(p_exchange, id, 'i64'); + return 21; + }, + }, + + godot_js_wrapper_interface_get__sig: 'ii', + godot_js_wrapper_interface_get: function (p_name) { + const name = GodotRuntime.parseString(p_name); + if (typeof (window[name]) !== 'undefined') { + return GodotJSWrapper.get_proxied(window[name]); + } + return 0; + }, + + godot_js_wrapper_object_get__sig: 'iiii', + godot_js_wrapper_object_get: function (p_id, p_exchange, p_prop) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return 0; + } + if (p_prop) { + const prop = GodotRuntime.parseString(p_prop); + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj); + return 0; // NIL + } + } + return GodotJSWrapper.js2variant(obj, p_exchange); + }, + + godot_js_wrapper_object_set__sig: 'viiii', + godot_js_wrapper_object_set: function (p_id, p_name, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return; + } + const name = GodotRuntime.parseString(p_name); + try { + obj[name] = GodotJSWrapper.variant2js(p_type, p_exchange); + } catch (e) { + GodotRuntime.error(`Error setting variable ${name} on object`, obj); + } + }, + + godot_js_wrapper_object_call__sig: 'iiiiiiiii', + godot_js_wrapper_object_call: function (p_id, p_method, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const method = GodotRuntime.parseString(p_method); + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = obj[method](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling method ${method} on:`, obj, 'error:', e); + return -1; + } + }, + + godot_js_wrapper_object_unref__sig: 'vi', + godot_js_wrapper_object_unref: function (p_id) { + const proxy = IDHandler.get(p_id); + if (proxy !== undefined) { + proxy.unref(); + } + }, + + godot_js_wrapper_create_cb__sig: 'vii', + godot_js_wrapper_create_cb: function (p_ref, p_func) { + const func = GodotRuntime.get_func(p_func); + let id = 0; + const cb = function () { + if (!GodotJSWrapper.get_proxied_value(id)) { + return; + } + const args = Array.from(arguments); + func(p_ref, GodotJSWrapper.get_proxied(args), args.length); + }; + id = GodotJSWrapper.get_proxied(cb); + return id; + }, + + godot_js_wrapper_object_getvar__sig: 'iiii', + godot_js_wrapper_object_getvar: function (p_id, p_type, p_exchange) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const prop = GodotJSWrapper.variant2js(p_type, p_exchange); + if (prop === undefined || prop === null) { + return -1; + } + try { + return GodotJSWrapper.js2variant(obj[prop], p_exchange); + } catch (e) { + GodotRuntime.error(`Error getting variable ${prop} on object`, obj, e); + return -1; + } + }, + + godot_js_wrapper_object_setvar__sig: 'iiiiii', + godot_js_wrapper_object_setvar: function (p_id, p_key_type, p_key_ex, p_val_type, p_val_ex) { + const obj = GodotJSWrapper.get_proxied_value(p_id); + if (obj === undefined) { + return -1; + } + const key = GodotJSWrapper.variant2js(p_key_type, p_key_ex); + try { + obj[key] = GodotJSWrapper.variant2js(p_val_type, p_val_ex); + return 0; + } catch (e) { + GodotRuntime.error(`Error setting variable ${key} on object`, obj); + return -1; + } + }, + + godot_js_wrapper_create_object__sig: 'iiiiiiii', + godot_js_wrapper_create_object: function (p_object, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) { + const name = GodotRuntime.parseString(p_object); + if (typeof (window[name]) === 'undefined') { + return -1; + } + const convert = GodotRuntime.get_func(p_convert_callback); + const freeLock = GodotRuntime.get_func(p_free_lock_callback); + const args = new Array(p_argc); + for (let i = 0; i < p_argc; i++) { + const type = convert(p_args, i, p_exchange, p_lock); + const lock = GodotRuntime.getHeapValue(p_lock, '*'); + args[i] = GodotJSWrapper.variant2js(type, p_exchange); + if (lock) { + freeLock(p_lock, type); + } + } + try { + const res = new window[name](...args); + return GodotJSWrapper.js2variant(res, p_exchange); + } catch (e) { + GodotRuntime.error(`Error calling constructor ${name} with args:`, args, 'error:', e); + return -1; + } + }, +}; + +autoAddDeps(GodotJSWrapper, '$GodotJSWrapper'); +mergeInto(LibraryManager.library, GodotJSWrapper); + +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; + try { + if (p_use_global_ctx) { + // indirect eval call grants global execution context + const global_eval = eval; // eslint-disable-line no-eval + eval_ret = global_eval(js_code); + } else { + eval_ret = eval(js_code); // eslint-disable-line no-eval + } + } catch (e) { + GodotRuntime.error(e); + } + + switch (typeof eval_ret) { + case 'boolean': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); + return 1; // BOOL + + case 'number': + GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); + return 3; // REAL + + case 'string': + GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); + return 4; // STRING + + case 'object': + if (eval_ret === null) { + break; + } + + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + const func = GodotRuntime.get_func(p_callback); + const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + break; + + // no default + } + return 0; // NIL + }, +}; + +mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 1d9f889bce..7414b8cc47 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -304,6 +304,23 @@ const GodotOS = { godot_js_os_hw_concurrency_get: function () { return navigator.hardwareConcurrency || 1; }, + + godot_js_os_download_buffer__sig: 'viiii', + godot_js_os_download_buffer: function (p_ptr, p_size, p_name, p_mime) { + const buf = GodotRuntime.heapSlice(HEAP8, p_ptr, p_size); + const name = GodotRuntime.parseString(p_name); + const mime = GodotRuntime.parseString(p_mime); + const blob = new Blob([buf], { type: mime }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = name; + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }, }; autoAddDeps(GodotOS, '$GodotOS'); diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json index b8c434b3dd..8bf5c52ff6 100644 --- a/platform/javascript/package-lock.json +++ b/platform/javascript/package-lock.json @@ -44,9 +44,9 @@ } }, "@babel/parser": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", - "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==", "dev": true }, "@eslint/eslintrc": { @@ -812,9 +812,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "ignore": { @@ -962,9 +962,8 @@ } }, "jsdoc": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.6.tgz", - "integrity": "sha512-znR99e1BHeyEkSvgDDpX0sTiTu+8aQyDl9DawrkOGZTTW8hv0deIFXx87114zJ7gRaDZKVQD/4tr1ifmJp9xhQ==", + "version": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560", + "from": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560", "dev": true, "requires": { "@babel/parser": "^7.9.4", @@ -975,12 +974,12 @@ "klaw": "^3.0.0", "markdown-it": "^10.0.0", "markdown-it-anchor": "^5.2.7", - "marked": "^0.8.2", + "marked": "^2.0.3", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", "strip-json-comments": "^3.1.0", "taffydb": "2.6.2", - "underscore": "~1.10.2" + "underscore": "~1.12.1" }, "dependencies": { "escape-string-regexp": { @@ -1069,9 +1068,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "markdown-it": { @@ -1094,9 +1093,9 @@ "dev": true }, "marked": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", - "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz", + "integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==", "dev": true }, "mdurl": { @@ -1689,9 +1688,9 @@ "dev": true }, "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "dev": true }, "uri-js": { diff --git a/platform/javascript/package.json b/platform/javascript/package.json index d9d272923e..53748503f9 100644 --- a/platform/javascript/package.json +++ b/platform/javascript/package.json @@ -23,6 +23,6 @@ "eslint": "^7.9.0", "eslint-config-airbnb-base": "^14.2.0", "eslint-plugin-import": "^2.22.0", - "jsdoc": "^3.6.6" + "jsdoc": "github:jsdoc/jsdoc#544a992824631fc652183d8b0b4b1281dc961560" } } |